replace chrono with time (#806)
* replace chrono with time * Fix test chrono usage --------- Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
This commit is contained in:
parent
2342a33923
commit
f90c01f702
35 changed files with 358 additions and 385 deletions
131
Cargo.lock
generated
131
Cargo.lock
generated
|
@ -54,15 +54,6 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android_system_properties"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -160,7 +151,6 @@ dependencies = [
|
||||||
"atuin-server",
|
"atuin-server",
|
||||||
"atuin-server-postgres",
|
"atuin-server-postgres",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"colored",
|
"colored",
|
||||||
|
@ -181,6 +171,7 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"time",
|
||||||
"tiny-bip39",
|
"tiny-bip39",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -197,7 +188,6 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"config",
|
"config",
|
||||||
"crypto_secretbox",
|
"crypto_secretbox",
|
||||||
|
@ -229,6 +219,7 @@ dependencies = [
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"sql-builder",
|
"sql-builder",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
@ -240,12 +231,12 @@ dependencies = [
|
||||||
name = "atuin-common"
|
name = "atuin-common"
|
||||||
version = "16.0.0"
|
version = "16.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"eyre",
|
"eyre",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"time",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -260,8 +251,6 @@ dependencies = [
|
||||||
"atuin-server-database",
|
"atuin-server-database",
|
||||||
"axum",
|
"axum",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"chrono",
|
|
||||||
"chronoutil",
|
|
||||||
"config",
|
"config",
|
||||||
"eyre",
|
"eyre",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
|
@ -271,6 +260,7 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
@ -284,10 +274,9 @@ version = "16.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
"chrono",
|
|
||||||
"chronoutil",
|
|
||||||
"eyre",
|
"eyre",
|
||||||
"serde",
|
"serde",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -299,10 +288,10 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atuin-common",
|
"atuin-common",
|
||||||
"atuin-server-database",
|
"atuin-server-database",
|
||||||
"chrono",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -515,31 +504,6 @@ dependencies = [
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
|
||||||
dependencies = [
|
|
||||||
"iana-time-zone",
|
|
||||||
"js-sys",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
"serde",
|
|
||||||
"time 0.1.45",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chronoutil"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a58c924bb772aa201da3acf5308c46b60275c64e6d3bc89c23dd63d71e83fd"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -838,6 +802,9 @@ name = "deranged"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diff"
|
name = "diff"
|
||||||
|
@ -1419,29 +1386,6 @@ dependencies = [
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone"
|
|
||||||
version = "0.1.57"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
|
||||||
dependencies = [
|
|
||||||
"android_system_properties",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"iana-time-zone-haiku",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone-haiku"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1521,8 +1465,8 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ffd2ac8397b9574daa4ffa7ede4427dd249cadaa900719d4b01154a5631d38b"
|
checksum = "9ffd2ac8397b9574daa4ffa7ede4427dd249cadaa900719d4b01154a5631d38b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"logos",
|
"logos",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1846,6 +1790,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "number_prefix"
|
name = "number_prefix"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -2478,7 +2431,7 @@ dependencies = [
|
||||||
"iso8601",
|
"iso8601",
|
||||||
"ring",
|
"ring",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time 0.3.25",
|
"time",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2817,7 +2770,6 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
@ -2844,6 +2796,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlformat",
|
"sqlformat",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2902,7 +2855,6 @@ dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
|
||||||
"crc",
|
"crc",
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
@ -2930,6 +2882,7 @@ dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
@ -2945,7 +2898,6 @@ dependencies = [
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
|
||||||
"crc",
|
"crc",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
|
@ -2971,6 +2923,7 @@ dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
"whoami",
|
"whoami",
|
||||||
|
@ -2983,7 +2936,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2"
|
checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"chrono",
|
|
||||||
"flume",
|
"flume",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -2995,6 +2947,7 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
@ -3104,23 +3057,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.45"
|
version = "0.3.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
checksum = "a79d09ac6b08c1ab3906a2f7cc2e81a0e27c7ae89c63812df75e52bef0751e07"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.3.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
|
@ -3134,9 +3078,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.11"
|
version = "0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
|
checksum = "75c65469ed6b3a4809d987a41eb1dc918e9bc1d92211cbad7ae82931846f7451"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
@ -3523,12 +3467,6 @@ version = "0.9.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.10.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -3661,15 +3599,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows"
|
|
||||||
version = "0.48.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.48.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -12,7 +12,7 @@ members = [
|
||||||
name = "atuin"
|
name = "atuin"
|
||||||
version = "16.0.0"
|
version = "16.0.0"
|
||||||
authors = ["Ellie Huxtable <ellie@elliehuxtable.com>"]
|
authors = ["Ellie Huxtable <ellie@elliehuxtable.com>"]
|
||||||
rust-version = "1.59"
|
rust-version = "1.67"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
homepage = "https://atuin.sh"
|
homepage = "https://atuin.sh"
|
||||||
repository = "https://github.com/atuinsh/atuin"
|
repository = "https://github.com/atuinsh/atuin"
|
||||||
|
@ -22,13 +22,13 @@ readme = "README.md"
|
||||||
async-trait = "0.1.58"
|
async-trait = "0.1.58"
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
time = { version = "0.3", features = ["serde-human-readable", "macros", "local-offset"] }
|
||||||
clap = { version = "4.0.18", features = ["derive"] }
|
clap = { version = "4.0.18", features = ["derive"] }
|
||||||
config = { version = "0.13", default-features = false, features = ["toml"] }
|
config = { version = "0.13", default-features = false, features = ["toml"] }
|
||||||
directories = "4"
|
directories = "4"
|
||||||
eyre = "0.6"
|
eyre = "0.6"
|
||||||
fs-err = "2.9"
|
fs-err = "2.9"
|
||||||
interim = { version = "0.1.0", features = ["chrono"] }
|
interim = { version = "0.1.0", features = ["time"] }
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
rand = { version = "0.8.5", features = ["std"] }
|
rand = { version = "0.8.5", features = ["std"] }
|
||||||
semver = "1.0.14"
|
semver = "1.0.14"
|
||||||
|
@ -50,4 +50,9 @@ default-features = false
|
||||||
|
|
||||||
[workspace.dependencies.sqlx]
|
[workspace.dependencies.sqlx]
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
features = ["runtime-tokio-rustls", "chrono", "postgres", "uuid"]
|
features = [
|
||||||
|
"runtime-tokio-rustls",
|
||||||
|
"time",
|
||||||
|
"postgres",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
|
@ -3,6 +3,7 @@ name = "atuin-client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "client library for atuin"
|
description = "client library for atuin"
|
||||||
|
|
||||||
|
rust-version = { workspace = true }
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
@ -20,7 +21,7 @@ atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
chrono = { workspace = true }
|
time = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
eyre = { workspace = true }
|
eyre = { workspace = true }
|
||||||
directories = { workspace = true }
|
directories = { workspace = true }
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
use eyre::{bail, Result};
|
use eyre::{bail, Result};
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{HeaderMap, AUTHORIZATION, USER_AGENT},
|
header::{HeaderMap, AUTHORIZATION, USER_AGENT},
|
||||||
|
@ -17,6 +16,8 @@ use atuin_common::{
|
||||||
record::RecordIndex,
|
record::RecordIndex,
|
||||||
};
|
};
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{history::History, sync::hash_str};
|
use crate::{history::History, sync::hash_str};
|
||||||
|
|
||||||
|
@ -150,8 +151,8 @@ impl<'a> Client<'a> {
|
||||||
|
|
||||||
pub async fn get_history(
|
pub async fn get_history(
|
||||||
&self,
|
&self,
|
||||||
sync_ts: chrono::DateTime<Utc>,
|
sync_ts: OffsetDateTime,
|
||||||
history_ts: chrono::DateTime<Utc>,
|
history_ts: OffsetDateTime,
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
) -> Result<SyncHistoryResponse> {
|
) -> Result<SyncHistoryResponse> {
|
||||||
let host = host.unwrap_or_else(|| {
|
let host = host.unwrap_or_else(|| {
|
||||||
|
@ -165,8 +166,8 @@ impl<'a> Client<'a> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}/sync/history?sync_ts={}&history_ts={}&host={}",
|
"{}/sync/history?sync_ts={}&history_ts={}&host={}",
|
||||||
self.sync_addr,
|
self.sync_addr,
|
||||||
urlencoding::encode(sync_ts.to_rfc3339().as_str()),
|
urlencoding::encode(sync_ts.format(&Rfc3339)?.as_str()),
|
||||||
urlencoding::encode(history_ts.to_rfc3339().as_str()),
|
urlencoding::encode(history_ts.format(&Rfc3339)?.as_str()),
|
||||||
host,
|
host,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ use std::{
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use atuin_common::utils;
|
use atuin_common::utils;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -17,6 +16,7 @@ use sqlx::{
|
||||||
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow},
|
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqlitePoolOptions, SqliteRow},
|
||||||
Result, Row,
|
Result, Row,
|
||||||
};
|
};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
history::History,
|
history::History,
|
||||||
|
@ -81,18 +81,14 @@ pub trait Database: Send + Sync + 'static {
|
||||||
max: Option<usize>,
|
max: Option<usize>,
|
||||||
unique: bool,
|
unique: bool,
|
||||||
) -> Result<Vec<History>>;
|
) -> Result<Vec<History>>;
|
||||||
async fn range(
|
async fn range(&self, from: OffsetDateTime, to: OffsetDateTime) -> Result<Vec<History>>;
|
||||||
&self,
|
|
||||||
from: chrono::DateTime<Utc>,
|
|
||||||
to: chrono::DateTime<Utc>,
|
|
||||||
) -> Result<Vec<History>>;
|
|
||||||
|
|
||||||
async fn update(&self, h: &History) -> Result<()>;
|
async fn update(&self, h: &History) -> Result<()>;
|
||||||
async fn history_count(&self) -> Result<i64>;
|
async fn history_count(&self) -> Result<i64>;
|
||||||
|
|
||||||
async fn first(&self) -> Result<History>;
|
async fn first(&self) -> Result<History>;
|
||||||
async fn last(&self) -> Result<History>;
|
async fn last(&self) -> Result<History>;
|
||||||
async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>;
|
async fn before(&self, timestamp: OffsetDateTime, count: i64) -> Result<Vec<History>>;
|
||||||
|
|
||||||
async fn delete(&self, mut h: History) -> Result<()>;
|
async fn delete(&self, mut h: History) -> Result<()>;
|
||||||
async fn deleted(&self) -> Result<Vec<History>>;
|
async fn deleted(&self) -> Result<Vec<History>>;
|
||||||
|
@ -158,14 +154,14 @@ impl Sqlite {
|
||||||
values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
values(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||||
)
|
)
|
||||||
.bind(h.id.as_str())
|
.bind(h.id.as_str())
|
||||||
.bind(h.timestamp.timestamp_nanos())
|
.bind(h.timestamp.unix_timestamp_nanos() as i64)
|
||||||
.bind(h.duration)
|
.bind(h.duration)
|
||||||
.bind(h.exit)
|
.bind(h.exit)
|
||||||
.bind(h.command.as_str())
|
.bind(h.command.as_str())
|
||||||
.bind(h.cwd.as_str())
|
.bind(h.cwd.as_str())
|
||||||
.bind(h.session.as_str())
|
.bind(h.session.as_str())
|
||||||
.bind(h.hostname.as_str())
|
.bind(h.hostname.as_str())
|
||||||
.bind(h.deleted_at.map(|t|t.timestamp_nanos()))
|
.bind(h.deleted_at.map(|t|t.unix_timestamp_nanos() as i64))
|
||||||
.execute(&mut **tx)
|
.execute(&mut **tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -177,14 +173,19 @@ impl Sqlite {
|
||||||
|
|
||||||
History::from_db()
|
History::from_db()
|
||||||
.id(row.get("id"))
|
.id(row.get("id"))
|
||||||
.timestamp(Utc.timestamp_nanos(row.get("timestamp")))
|
.timestamp(
|
||||||
|
OffsetDateTime::from_unix_timestamp_nanos(row.get::<i64, _>("timestamp") as i128)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
.duration(row.get("duration"))
|
.duration(row.get("duration"))
|
||||||
.exit(row.get("exit"))
|
.exit(row.get("exit"))
|
||||||
.command(row.get("command"))
|
.command(row.get("command"))
|
||||||
.cwd(row.get("cwd"))
|
.cwd(row.get("cwd"))
|
||||||
.session(row.get("session"))
|
.session(row.get("session"))
|
||||||
.hostname(row.get("hostname"))
|
.hostname(row.get("hostname"))
|
||||||
.deleted_at(deleted_at.map(|t| Utc.timestamp_nanos(t)))
|
.deleted_at(
|
||||||
|
deleted_at.and_then(|t| OffsetDateTime::from_unix_timestamp_nanos(t as i128).ok()),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -236,14 +237,14 @@ impl Database for Sqlite {
|
||||||
where id = ?1",
|
where id = ?1",
|
||||||
)
|
)
|
||||||
.bind(h.id.as_str())
|
.bind(h.id.as_str())
|
||||||
.bind(h.timestamp.timestamp_nanos())
|
.bind(h.timestamp.unix_timestamp_nanos() as i64)
|
||||||
.bind(h.duration)
|
.bind(h.duration)
|
||||||
.bind(h.exit)
|
.bind(h.exit)
|
||||||
.bind(h.command.as_str())
|
.bind(h.command.as_str())
|
||||||
.bind(h.cwd.as_str())
|
.bind(h.cwd.as_str())
|
||||||
.bind(h.session.as_str())
|
.bind(h.session.as_str())
|
||||||
.bind(h.hostname.as_str())
|
.bind(h.hostname.as_str())
|
||||||
.bind(h.deleted_at.map(|t|t.timestamp_nanos()))
|
.bind(h.deleted_at.map(|t|t.unix_timestamp_nanos() as i64))
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -292,18 +293,14 @@ impl Database for Sqlite {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn range(
|
async fn range(&self, from: OffsetDateTime, to: OffsetDateTime) -> Result<Vec<History>> {
|
||||||
&self,
|
|
||||||
from: chrono::DateTime<Utc>,
|
|
||||||
to: chrono::DateTime<Utc>,
|
|
||||||
) -> Result<Vec<History>> {
|
|
||||||
debug!("listing history from {:?} to {:?}", from, to);
|
debug!("listing history from {:?} to {:?}", from, to);
|
||||||
|
|
||||||
let res = sqlx::query(
|
let res = sqlx::query(
|
||||||
"select * from history where timestamp >= ?1 and timestamp <= ?2 order by timestamp asc",
|
"select * from history where timestamp >= ?1 and timestamp <= ?2 order by timestamp asc",
|
||||||
)
|
)
|
||||||
.bind(from.timestamp_nanos())
|
.bind(from.unix_timestamp_nanos() as i64)
|
||||||
.bind(to.timestamp_nanos())
|
.bind(to.unix_timestamp_nanos() as i64)
|
||||||
.map(Self::query_history)
|
.map(Self::query_history)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -332,11 +329,11 @@ impl Database for Sqlite {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>> {
|
async fn before(&self, timestamp: OffsetDateTime, count: i64) -> Result<Vec<History>> {
|
||||||
let res = sqlx::query(
|
let res = sqlx::query(
|
||||||
"select * from history where timestamp < ?1 order by timestamp desc limit ?2",
|
"select * from history where timestamp < ?1 order by timestamp desc limit ?2",
|
||||||
)
|
)
|
||||||
.bind(timestamp.timestamp_nanos())
|
.bind(timestamp.unix_timestamp_nanos() as i64)
|
||||||
.bind(count)
|
.bind(count)
|
||||||
.map(Self::query_history)
|
.map(Self::query_history)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
@ -471,13 +468,23 @@ impl Database for Sqlite {
|
||||||
.map(|exclude_cwd| sql.and_where_ne("cwd", quote(exclude_cwd)));
|
.map(|exclude_cwd| sql.and_where_ne("cwd", quote(exclude_cwd)));
|
||||||
|
|
||||||
filter_options.before.map(|before| {
|
filter_options.before.map(|before| {
|
||||||
interim::parse_date_string(before.as_str(), Utc::now(), interim::Dialect::Uk)
|
interim::parse_date_string(
|
||||||
.map(|before| sql.and_where_lt("timestamp", quote(before.timestamp_nanos())))
|
before.as_str(),
|
||||||
|
OffsetDateTime::now_utc(),
|
||||||
|
interim::Dialect::Uk,
|
||||||
|
)
|
||||||
|
.map(|before| {
|
||||||
|
sql.and_where_lt("timestamp", quote(before.unix_timestamp_nanos() as i64))
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
filter_options.after.map(|after| {
|
filter_options.after.map(|after| {
|
||||||
interim::parse_date_string(after.as_str(), Utc::now(), interim::Dialect::Uk)
|
interim::parse_date_string(
|
||||||
.map(|after| sql.and_where_gt("timestamp", quote(after.timestamp_nanos())))
|
after.as_str(),
|
||||||
|
OffsetDateTime::now_utc(),
|
||||||
|
interim::Dialect::Uk,
|
||||||
|
)
|
||||||
|
.map(|after| sql.and_where_gt("timestamp", quote(after.unix_timestamp_nanos() as i64)))
|
||||||
});
|
});
|
||||||
|
|
||||||
sql.and_where_is_null("deleted_at");
|
sql.and_where_is_null("deleted_at");
|
||||||
|
@ -540,7 +547,7 @@ impl Database for Sqlite {
|
||||||
// deleted_at doesn't mean the actual time that the user deleted it,
|
// deleted_at doesn't mean the actual time that the user deleted it,
|
||||||
// but the time that the system marks it as deleted
|
// but the time that the system marks it as deleted
|
||||||
async fn delete(&self, mut h: History) -> Result<()> {
|
async fn delete(&self, mut h: History) -> Result<()> {
|
||||||
let now = chrono::Utc::now();
|
let now = OffsetDateTime::now_utc();
|
||||||
h.command = rand::thread_rng()
|
h.command = rand::thread_rng()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
.take(32)
|
.take(32)
|
||||||
|
@ -612,7 +619,7 @@ mod test {
|
||||||
|
|
||||||
async fn new_history_item(db: &mut impl Database, cmd: &str) -> Result<()> {
|
async fn new_history_item(db: &mut impl Database, cmd: &str) -> Result<()> {
|
||||||
let mut captured: History = History::capture()
|
let mut captured: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(OffsetDateTime::now_utc())
|
||||||
.command(cmd)
|
.command(cmd)
|
||||||
.cwd("/home/ellie")
|
.cwd("/home/ellie")
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
use std::{io::prelude::*, path::PathBuf};
|
use std::{io::prelude::*, path::PathBuf};
|
||||||
|
|
||||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
pub use crypto_secretbox::Key;
|
pub use crypto_secretbox::Key;
|
||||||
use crypto_secretbox::{
|
use crypto_secretbox::{
|
||||||
aead::{Nonce, OsRng},
|
aead::{Nonce, OsRng},
|
||||||
|
@ -21,6 +20,7 @@ use eyre::{bail, ensure, eyre, Context, Result};
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use rmp::{decode::Bytes, Marker};
|
use rmp::{decode::Bytes, Marker};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use time::{format_description::well_known::Rfc3339, macros::format_description, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{history::History, settings::Settings};
|
use crate::{history::History, settings::Settings};
|
||||||
|
|
||||||
|
@ -137,6 +137,28 @@ pub fn decrypt(mut encrypted_history: EncryptedHistory, key: &Key) -> Result<His
|
||||||
Ok(history)
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_rfc3339(ts: OffsetDateTime) -> Result<String> {
|
||||||
|
// horrible hack. chrono AutoSI limits to 0, 3, 6, or 9 decimal places for nanoseconds.
|
||||||
|
// time does not have this functionality.
|
||||||
|
static PARTIAL_RFC3339_0: &[time::format_description::FormatItem<'static>] =
|
||||||
|
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z");
|
||||||
|
static PARTIAL_RFC3339_3: &[time::format_description::FormatItem<'static>] =
|
||||||
|
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z");
|
||||||
|
static PARTIAL_RFC3339_6: &[time::format_description::FormatItem<'static>] =
|
||||||
|
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:6]Z");
|
||||||
|
static PARTIAL_RFC3339_9: &[time::format_description::FormatItem<'static>] =
|
||||||
|
format_description!("[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:9]Z");
|
||||||
|
|
||||||
|
let fmt = match ts.nanosecond() {
|
||||||
|
0 => PARTIAL_RFC3339_0,
|
||||||
|
ns if ns % 1_000_000 == 0 => PARTIAL_RFC3339_3,
|
||||||
|
ns if ns % 1_000 == 0 => PARTIAL_RFC3339_6,
|
||||||
|
_ => PARTIAL_RFC3339_9,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ts.format(fmt)?)
|
||||||
|
}
|
||||||
|
|
||||||
fn encode(h: &History) -> Result<Vec<u8>> {
|
fn encode(h: &History) -> Result<Vec<u8>> {
|
||||||
use rmp::encode;
|
use rmp::encode;
|
||||||
|
|
||||||
|
@ -145,11 +167,7 @@ fn encode(h: &History) -> Result<Vec<u8>> {
|
||||||
encode::write_array_len(&mut output, 9)?;
|
encode::write_array_len(&mut output, 9)?;
|
||||||
|
|
||||||
encode::write_str(&mut output, &h.id)?;
|
encode::write_str(&mut output, &h.id)?;
|
||||||
encode::write_str(
|
encode::write_str(&mut output, &(format_rfc3339(h.timestamp)?))?;
|
||||||
&mut output,
|
|
||||||
&(h.timestamp
|
|
||||||
.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)),
|
|
||||||
)?;
|
|
||||||
encode::write_sint(&mut output, h.duration)?;
|
encode::write_sint(&mut output, h.duration)?;
|
||||||
encode::write_sint(&mut output, h.exit)?;
|
encode::write_sint(&mut output, h.exit)?;
|
||||||
encode::write_str(&mut output, &h.command)?;
|
encode::write_str(&mut output, &h.command)?;
|
||||||
|
@ -157,10 +175,7 @@ fn encode(h: &History) -> Result<Vec<u8>> {
|
||||||
encode::write_str(&mut output, &h.session)?;
|
encode::write_str(&mut output, &h.session)?;
|
||||||
encode::write_str(&mut output, &h.hostname)?;
|
encode::write_str(&mut output, &h.hostname)?;
|
||||||
match h.deleted_at {
|
match h.deleted_at {
|
||||||
Some(d) => encode::write_str(
|
Some(d) => encode::write_str(&mut output, &format_rfc3339(d)?)?,
|
||||||
&mut output,
|
|
||||||
&d.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true),
|
|
||||||
)?,
|
|
||||||
None => encode::write_nil(&mut output)?,
|
None => encode::write_nil(&mut output)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +235,7 @@ fn decode(bytes: &[u8]) -> Result<History> {
|
||||||
|
|
||||||
Ok(History {
|
Ok(History {
|
||||||
id: id.to_owned(),
|
id: id.to_owned(),
|
||||||
timestamp: DateTime::parse_from_rfc3339(timestamp)?.with_timezone(&Utc),
|
timestamp: OffsetDateTime::parse(timestamp, &Rfc3339)?,
|
||||||
duration,
|
duration,
|
||||||
exit,
|
exit,
|
||||||
command: command.to_owned(),
|
command: command.to_owned(),
|
||||||
|
@ -228,9 +243,8 @@ fn decode(bytes: &[u8]) -> Result<History> {
|
||||||
session: session.to_owned(),
|
session: session.to_owned(),
|
||||||
hostname: hostname.to_owned(),
|
hostname: hostname.to_owned(),
|
||||||
deleted_at: deleted_at
|
deleted_at: deleted_at
|
||||||
.map(DateTime::parse_from_rfc3339)
|
.map(|t| OffsetDateTime::parse(t, &Rfc3339))
|
||||||
.transpose()?
|
.transpose()?,
|
||||||
.map(|dt| dt.with_timezone(&Utc)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +255,8 @@ fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crypto_secretbox::{aead::OsRng, KeyInit, XSalsa20Poly1305};
|
use crypto_secretbox::{aead::OsRng, KeyInit, XSalsa20Poly1305};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use time::{macros::datetime, OffsetDateTime};
|
||||||
|
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
|
||||||
|
@ -253,7 +269,7 @@ mod test {
|
||||||
|
|
||||||
let history = History::from_db()
|
let history = History::from_db()
|
||||||
.id("1".into())
|
.id("1".into())
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(OffsetDateTime::now_utc())
|
||||||
.command("ls".into())
|
.command("ls".into())
|
||||||
.cwd("/home/ellie".into())
|
.cwd("/home/ellie".into())
|
||||||
.exit(0)
|
.exit(0)
|
||||||
|
@ -297,7 +313,7 @@ mod test {
|
||||||
];
|
];
|
||||||
let history = History {
|
let history = History {
|
||||||
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
||||||
timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(),
|
timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00),
|
||||||
duration: 49206000,
|
duration: 49206000,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
command: "git status".to_owned(),
|
command: "git status".to_owned(),
|
||||||
|
@ -318,14 +334,14 @@ mod test {
|
||||||
fn test_decode_deleted() {
|
fn test_decode_deleted() {
|
||||||
let history = History {
|
let history = History {
|
||||||
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
||||||
timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(),
|
timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00),
|
||||||
duration: 49206000,
|
duration: 49206000,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
command: "git status".to_owned(),
|
command: "git status".to_owned(),
|
||||||
cwd: "/Users/conrad.ludgate/Documents/code/atuin".to_owned(),
|
cwd: "/Users/conrad.ludgate/Documents/code/atuin".to_owned(),
|
||||||
session: "b97d9a306f274473a203d2eba41f9457".to_owned(),
|
session: "b97d9a306f274473a203d2eba41f9457".to_owned(),
|
||||||
hostname: "fvfg936c0kpf:conrad.ludgate".to_owned(),
|
hostname: "fvfg936c0kpf:conrad.ludgate".to_owned(),
|
||||||
deleted_at: Some("2023-05-28T18:35:40.633872Z".parse().unwrap()),
|
deleted_at: Some(datetime!(2023-05-28 18:35:40.633872 +00:00)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = encode(&history).unwrap();
|
let b = encode(&history).unwrap();
|
||||||
|
@ -349,7 +365,7 @@ mod test {
|
||||||
];
|
];
|
||||||
let history = History {
|
let history = History {
|
||||||
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
id: "66d16cbee7cd47538e5c5b8b44e9006e".to_owned(),
|
||||||
timestamp: "2023-05-28T18:35:40.633872Z".parse().unwrap(),
|
timestamp: datetime!(2023-05-28 18:35:40.633872 +00:00),
|
||||||
duration: 49206000,
|
duration: 49206000,
|
||||||
exit: 0,
|
exit: 0,
|
||||||
command: "git status".to_owned(),
|
command: "git status".to_owned(),
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
use atuin_common::utils::uuid_v7;
|
use atuin_common::utils::uuid_v7;
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
|
|
||||||
use crate::{secrets::SECRET_PATTERNS, settings::Settings};
|
use crate::{secrets::SECRET_PATTERNS, settings::Settings};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
mod builder;
|
mod builder;
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ pub struct History {
|
||||||
/// Stored as `client_id` in the database.
|
/// Stored as `client_id` in the database.
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// When the command was run.
|
/// When the command was run.
|
||||||
pub timestamp: chrono::DateTime<Utc>,
|
pub timestamp: OffsetDateTime,
|
||||||
/// How long the command took to run.
|
/// How long the command took to run.
|
||||||
pub duration: i64,
|
pub duration: i64,
|
||||||
/// The exit code of the command.
|
/// The exit code of the command.
|
||||||
|
@ -43,20 +42,20 @@ pub struct History {
|
||||||
/// The hostname of the machine the command was run on.
|
/// The hostname of the machine the command was run on.
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
/// Timestamp, which is set when the entry is deleted, allowing a soft delete.
|
/// Timestamp, which is set when the entry is deleted, allowing a soft delete.
|
||||||
pub deleted_at: Option<chrono::DateTime<Utc>>,
|
pub deleted_at: Option<OffsetDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn new(
|
fn new(
|
||||||
timestamp: chrono::DateTime<Utc>,
|
timestamp: OffsetDateTime,
|
||||||
command: String,
|
command: String,
|
||||||
cwd: String,
|
cwd: String,
|
||||||
exit: i64,
|
exit: i64,
|
||||||
duration: i64,
|
duration: i64,
|
||||||
session: Option<String>,
|
session: Option<String>,
|
||||||
hostname: Option<String>,
|
hostname: Option<String>,
|
||||||
deleted_at: Option<chrono::DateTime<Utc>>,
|
deleted_at: Option<OffsetDateTime>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let session = session
|
let session = session
|
||||||
.or_else(|| env::var("ATUIN_SESSION").ok())
|
.or_else(|| env::var("ATUIN_SESSION").ok())
|
||||||
|
@ -91,7 +90,7 @@ impl History {
|
||||||
/// use atuin_client::history::History;
|
/// use atuin_client::history::History;
|
||||||
///
|
///
|
||||||
/// let history: History = History::import()
|
/// let history: History = History::import()
|
||||||
/// .timestamp(chrono::Utc::now())
|
/// .timestamp(time::OffsetDateTime::now_utc())
|
||||||
/// .command("ls -la")
|
/// .command("ls -la")
|
||||||
/// .build()
|
/// .build()
|
||||||
/// .into();
|
/// .into();
|
||||||
|
@ -102,7 +101,7 @@ impl History {
|
||||||
/// use atuin_client::history::History;
|
/// use atuin_client::history::History;
|
||||||
///
|
///
|
||||||
/// let history: History = History::import()
|
/// let history: History = History::import()
|
||||||
/// .timestamp(chrono::Utc::now())
|
/// .timestamp(time::OffsetDateTime::now_utc())
|
||||||
/// .command("ls -la")
|
/// .command("ls -la")
|
||||||
/// .cwd("/home/user")
|
/// .cwd("/home/user")
|
||||||
/// .exit(0)
|
/// .exit(0)
|
||||||
|
@ -138,7 +137,7 @@ impl History {
|
||||||
/// use atuin_client::history::History;
|
/// use atuin_client::history::History;
|
||||||
///
|
///
|
||||||
/// let history: History = History::capture()
|
/// let history: History = History::capture()
|
||||||
/// .timestamp(chrono::Utc::now())
|
/// .timestamp(time::OffsetDateTime::now_utc())
|
||||||
/// .command("ls -la")
|
/// .command("ls -la")
|
||||||
/// .cwd("/home/user")
|
/// .cwd("/home/user")
|
||||||
/// .build()
|
/// .build()
|
||||||
|
@ -152,7 +151,7 @@ impl History {
|
||||||
///
|
///
|
||||||
/// // this will not compile because `cwd` is missing
|
/// // this will not compile because `cwd` is missing
|
||||||
/// let history: History = History::capture()
|
/// let history: History = History::capture()
|
||||||
/// .timestamp(chrono::Utc::now())
|
/// .timestamp(time::OffsetDateTime::now_utc())
|
||||||
/// .command("ls -la")
|
/// .command("ls -la")
|
||||||
/// .build()
|
/// .build()
|
||||||
/// .into();
|
/// .into();
|
||||||
|
@ -170,7 +169,7 @@ impl History {
|
||||||
///
|
///
|
||||||
/// // this will not compile because `id` field is missing
|
/// // this will not compile because `id` field is missing
|
||||||
/// let history: History = History::from_db()
|
/// let history: History = History::from_db()
|
||||||
/// .timestamp(chrono::Utc::now())
|
/// .timestamp(time::OffsetDateTime::now_utc())
|
||||||
/// .command("ls -la".to_string())
|
/// .command("ls -la".to_string())
|
||||||
/// .cwd("/home/user".to_string())
|
/// .cwd("/home/user".to_string())
|
||||||
/// .exit(0)
|
/// .exit(0)
|
||||||
|
@ -216,35 +215,35 @@ mod tests {
|
||||||
settings.history_filter = RegexSet::new(["^psql"]).unwrap();
|
settings.history_filter = RegexSet::new(["^psql"]).unwrap();
|
||||||
|
|
||||||
let normal_command: History = History::capture()
|
let normal_command: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command("echo foo")
|
.command("echo foo")
|
||||||
.cwd("/")
|
.cwd("/")
|
||||||
.build()
|
.build()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let with_space: History = History::capture()
|
let with_space: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command(" echo bar")
|
.command(" echo bar")
|
||||||
.cwd("/")
|
.cwd("/")
|
||||||
.build()
|
.build()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let stripe_key: History = History::capture()
|
let stripe_key: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop")
|
.command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop")
|
||||||
.cwd("/")
|
.cwd("/")
|
||||||
.build()
|
.build()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let secret_dir: History = History::capture()
|
let secret_dir: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command("echo ohno")
|
.command("echo ohno")
|
||||||
.cwd("/supasecret")
|
.cwd("/supasecret")
|
||||||
.build()
|
.build()
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let with_psql: History = History::capture()
|
let with_psql: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command("psql")
|
.command("psql")
|
||||||
.cwd("/supasecret")
|
.cwd("/supasecret")
|
||||||
.build()
|
.build()
|
||||||
|
@ -263,7 +262,7 @@ mod tests {
|
||||||
settings.secrets_filter = false;
|
settings.secrets_filter = false;
|
||||||
|
|
||||||
let stripe_key: History = History::capture()
|
let stripe_key: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(time::OffsetDateTime::now_utc())
|
||||||
.command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop")
|
.command("curl foo.com/bar?key=sk_test_1234567890abcdefghijklmnop")
|
||||||
.cwd("/")
|
.cwd("/")
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use chrono::Utc;
|
|
||||||
use typed_builder::TypedBuilder;
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
use super::History;
|
use super::History;
|
||||||
|
@ -8,7 +7,7 @@ use super::History;
|
||||||
/// The only two required fields are `timestamp` and `command`.
|
/// The only two required fields are `timestamp` and `command`.
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone, TypedBuilder)]
|
||||||
pub struct HistoryImported {
|
pub struct HistoryImported {
|
||||||
timestamp: chrono::DateTime<Utc>,
|
timestamp: time::OffsetDateTime,
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
command: String,
|
command: String,
|
||||||
#[builder(default = "unknown".into(), setter(into))]
|
#[builder(default = "unknown".into(), setter(into))]
|
||||||
|
@ -45,7 +44,7 @@ impl From<HistoryImported> for History {
|
||||||
/// the command is finished, such as `exit` or `duration`.
|
/// the command is finished, such as `exit` or `duration`.
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone, TypedBuilder)]
|
||||||
pub struct HistoryCaptured {
|
pub struct HistoryCaptured {
|
||||||
timestamp: chrono::DateTime<Utc>,
|
timestamp: time::OffsetDateTime,
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
command: String,
|
command: String,
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
|
@ -73,14 +72,14 @@ impl From<HistoryCaptured> for History {
|
||||||
#[derive(Debug, Clone, TypedBuilder)]
|
#[derive(Debug, Clone, TypedBuilder)]
|
||||||
pub struct HistoryFromDb {
|
pub struct HistoryFromDb {
|
||||||
id: String,
|
id: String,
|
||||||
timestamp: chrono::DateTime<Utc>,
|
timestamp: time::OffsetDateTime,
|
||||||
command: String,
|
command: String,
|
||||||
cwd: String,
|
cwd: String,
|
||||||
exit: i64,
|
exit: i64,
|
||||||
duration: i64,
|
duration: i64,
|
||||||
session: String,
|
session: String,
|
||||||
hostname: String,
|
hostname: String,
|
||||||
deleted_at: Option<chrono::DateTime<Utc>>,
|
deleted_at: Option<time::OffsetDateTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HistoryFromDb> for History {
|
impl From<HistoryFromDb> for History {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::{fs::File, io::Read, path::PathBuf, str};
|
use std::{fs::File, io::Read, path::PathBuf, str};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
|
||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -55,7 +55,7 @@ impl Importer for Bash {
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
// if no known timestamps, use now as base
|
// if no known timestamps, use now as base
|
||||||
.unwrap_or((lines.len(), Utc::now()));
|
.unwrap_or((lines.len(), OffsetDateTime::now_utc()));
|
||||||
|
|
||||||
// if no timestamp is recorded, then use this increment to set an arbitrary timestamp
|
// if no timestamp is recorded, then use this increment to set an arbitrary timestamp
|
||||||
// to preserve ordering
|
// to preserve ordering
|
||||||
|
@ -99,7 +99,7 @@ enum LineType<'a> {
|
||||||
Empty,
|
Empty,
|
||||||
/// A timestamp line start with a '#', followed immediately by an integer
|
/// A timestamp line start with a '#', followed immediately by an integer
|
||||||
/// that represents seconds since UNIX epoch.
|
/// that represents seconds since UNIX epoch.
|
||||||
Timestamp(DateTime<Utc>),
|
Timestamp(OffsetDateTime),
|
||||||
/// Anything else.
|
/// Anything else.
|
||||||
Command(&'a str),
|
Command(&'a str),
|
||||||
}
|
}
|
||||||
|
@ -119,10 +119,9 @@ impl<'a> From<&'a [u8]> for LineType<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_parse_line_as_timestamp(line: &str) -> Option<DateTime<Utc>> {
|
fn try_parse_line_as_timestamp(line: &str) -> Option<OffsetDateTime> {
|
||||||
let seconds = line.strip_prefix('#')?.parse().ok()?;
|
let seconds = line.strip_prefix('#')?.parse().ok()?;
|
||||||
let time = NaiveDateTime::from_timestamp(seconds, 0);
|
OffsetDateTime::from_unix_timestamp(seconds).ok()
|
||||||
Some(DateTime::from_utc(time, Utc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -183,7 +182,7 @@ cd ../
|
||||||
["git reset", "git clean -dxf", "cd ../"],
|
["git reset", "git clean -dxf", "cd ../"],
|
||||||
);
|
);
|
||||||
assert_equal(
|
assert_equal(
|
||||||
loader.buf.iter().map(|h| h.timestamp.timestamp()),
|
loader.buf.iter().map(|h| h.timestamp.unix_timestamp()),
|
||||||
[1672918999, 1672919006, 1672919020],
|
[1672918999, 1672919006, 1672919020],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
use std::{fs::File, io::Read, path::PathBuf};
|
use std::{fs::File, io::Read, path::PathBuf};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use directories::BaseDirs;
|
use directories::BaseDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{unix_byte_lines, Importer, Loader};
|
use super::{unix_byte_lines, Importer, Loader};
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -59,8 +59,8 @@ impl Importer for Fish {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load(self, loader: &mut impl Loader) -> Result<()> {
|
async fn load(self, loader: &mut impl Loader) -> Result<()> {
|
||||||
let now = Utc::now();
|
let now = OffsetDateTime::now_utc();
|
||||||
let mut time: Option<DateTime<Utc>> = None;
|
let mut time: Option<OffsetDateTime> = None;
|
||||||
let mut cmd: Option<String> = None;
|
let mut cmd: Option<String> = None;
|
||||||
|
|
||||||
for b in unix_byte_lines(&self.bytes) {
|
for b in unix_byte_lines(&self.bytes) {
|
||||||
|
@ -89,7 +89,7 @@ impl Importer for Fish {
|
||||||
} else if let Some(t) = s.strip_prefix(" when: ") {
|
} else if let Some(t) = s.strip_prefix(" when: ") {
|
||||||
// if t is not an int, just ignore this line
|
// if t is not an int, just ignore this line
|
||||||
if let Ok(t) = t.parse::<i64>() {
|
if let Ok(t) = t.parse::<i64>() {
|
||||||
time = Some(Utc.timestamp(t, 0));
|
time = Some(OffsetDateTime::from_unix_timestamp(t)?);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ... ignore paths lines
|
// ... ignore paths lines
|
||||||
|
@ -164,7 +164,7 @@ ERROR
|
||||||
($timestamp:expr, $command:expr) => {
|
($timestamp:expr, $command:expr) => {
|
||||||
let h = history.next().expect("missing entry in history");
|
let h = history.next().expect("missing entry in history");
|
||||||
assert_eq!(h.command.as_str(), $command);
|
assert_eq!(h.command.as_str(), $command);
|
||||||
assert_eq!(h.timestamp.timestamp(), $timestamp);
|
assert_eq!(h.timestamp.unix_timestamp(), $timestamp);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::{fs::File, io::Read, path::PathBuf};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use directories::BaseDirs;
|
use directories::BaseDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{unix_byte_lines, Importer, Loader};
|
use super::{unix_byte_lines, Importer, Loader};
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -44,7 +45,7 @@ impl Importer for Nu {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load(self, h: &mut impl Loader) -> Result<()> {
|
async fn load(self, h: &mut impl Loader) -> Result<()> {
|
||||||
let now = chrono::Utc::now();
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
for b in unix_byte_lines(&self.bytes) {
|
for b in unix_byte_lines(&self.bytes) {
|
||||||
|
@ -55,7 +56,7 @@ impl Importer for Nu {
|
||||||
|
|
||||||
let cmd: String = s.replace("<\\n>", "\n");
|
let cmd: String = s.replace("<\\n>", "\n");
|
||||||
|
|
||||||
let offset = chrono::Duration::nanoseconds(counter);
|
let offset = time::Duration::nanoseconds(counter);
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
|
||||||
let entry = History::import().timestamp(now - offset).command(cmd);
|
let entry = History::import().timestamp(now - offset).command(cmd);
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use directories::BaseDirs;
|
use directories::BaseDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
use sqlx::{sqlite::SqlitePool, Pool};
|
use sqlx::{sqlite::SqlitePool, Pool};
|
||||||
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
use super::Importer;
|
use super::Importer;
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -31,10 +31,10 @@ impl From<HistDbEntry> for History {
|
||||||
let ts_secs = histdb_item.start_timestamp / 1000;
|
let ts_secs = histdb_item.start_timestamp / 1000;
|
||||||
let ts_ns = (histdb_item.start_timestamp % 1000) * 1_000_000;
|
let ts_ns = (histdb_item.start_timestamp % 1000) * 1_000_000;
|
||||||
let imported = History::import()
|
let imported = History::import()
|
||||||
.timestamp(DateTime::from_utc(
|
.timestamp(
|
||||||
NaiveDateTime::from_timestamp(ts_secs, ts_ns as u32),
|
OffsetDateTime::from_unix_timestamp(ts_secs).unwrap()
|
||||||
Utc,
|
+ Duration::nanoseconds(ts_ns),
|
||||||
))
|
)
|
||||||
.command(String::from_utf8(histdb_item.command_line).unwrap())
|
.command(String::from_utf8(histdb_item.command_line).unwrap())
|
||||||
.cwd(String::from_utf8(histdb_item.cwd).unwrap())
|
.cwd(String::from_utf8(histdb_item.cwd).unwrap())
|
||||||
.exit(histdb_item.exit_status)
|
.exit(histdb_item.exit_status)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{fs::File, io::Read, path::PathBuf};
|
use std::{fs::File, io::Read, path::PathBuf};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{TimeZone, Utc};
|
|
||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use atuin_common::utils::uuid_v7;
|
use atuin_common::utils::uuid_v7;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -110,16 +110,18 @@ impl Importer for Resh {
|
||||||
#[allow(clippy::cast_sign_loss)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
let timestamp = {
|
let timestamp = {
|
||||||
let secs = entry.realtime_before.floor() as i64;
|
let secs = entry.realtime_before.floor() as i64;
|
||||||
let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as u32;
|
let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as i64;
|
||||||
Utc.timestamp(secs, nanosecs)
|
OffsetDateTime::from_unix_timestamp(secs)? + time::Duration::nanoseconds(nanosecs)
|
||||||
};
|
};
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
#[allow(clippy::cast_sign_loss)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
let duration = {
|
let duration = {
|
||||||
let secs = entry.realtime_after.floor() as i64;
|
let secs = entry.realtime_after.floor() as i64;
|
||||||
let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as u32;
|
let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as i64;
|
||||||
let difference = Utc.timestamp(secs, nanosecs) - timestamp;
|
let base = OffsetDateTime::from_unix_timestamp(secs)?
|
||||||
difference.num_nanoseconds().unwrap_or(0)
|
+ time::Duration::nanoseconds(nanosecs);
|
||||||
|
let difference = base - timestamp;
|
||||||
|
difference.whole_nanoseconds() as i64
|
||||||
};
|
};
|
||||||
|
|
||||||
let imported = History::import()
|
let imported = History::import()
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
use std::{fs::File, io::Read, path::PathBuf};
|
use std::{fs::File, io::Read, path::PathBuf};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
use super::{get_histpath, unix_byte_lines, Importer, Loader};
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -58,7 +58,7 @@ impl Importer for Zsh {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load(self, h: &mut impl Loader) -> Result<()> {
|
async fn load(self, h: &mut impl Loader) -> Result<()> {
|
||||||
let now = chrono::Utc::now();
|
let now = OffsetDateTime::now_utc();
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
|
@ -79,7 +79,7 @@ impl Importer for Zsh {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
h.push(parse_extended(command, counter)).await?;
|
h.push(parse_extended(command, counter)).await?;
|
||||||
} else {
|
} else {
|
||||||
let offset = chrono::Duration::seconds(counter);
|
let offset = time::Duration::seconds(counter);
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
|
||||||
let imported = History::import()
|
let imported = History::import()
|
||||||
|
@ -102,11 +102,10 @@ fn parse_extended(line: &str, counter: i64) -> History {
|
||||||
|
|
||||||
let time = time
|
let time = time
|
||||||
.parse::<i64>()
|
.parse::<i64>()
|
||||||
.unwrap_or_else(|_| chrono::Utc::now().timestamp());
|
.ok()
|
||||||
|
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
|
||||||
let offset = chrono::Duration::milliseconds(counter);
|
.unwrap_or_else(OffsetDateTime::now_utc)
|
||||||
let time = Utc.timestamp(time, 0);
|
+ time::Duration::milliseconds(counter);
|
||||||
let time = time + offset;
|
|
||||||
|
|
||||||
// use nanos, because why the hell not? we won't display them.
|
// use nanos, because why the hell not? we won't display them.
|
||||||
let duration = duration.parse::<i64>().map_or(-1, |t| t * 1_000_000_000);
|
let duration = duration.parse::<i64>().map_or(-1, |t| t * 1_000_000_000);
|
||||||
|
@ -121,8 +120,6 @@ fn parse_extended(line: &str, counter: i64) -> History {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use chrono::prelude::*;
|
|
||||||
use chrono::Utc;
|
|
||||||
use itertools::assert_equal;
|
use itertools::assert_equal;
|
||||||
|
|
||||||
use crate::import::tests::TestLoader;
|
use crate::import::tests::TestLoader;
|
||||||
|
@ -135,25 +132,37 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(parsed.command, "cargo install atuin");
|
assert_eq!(parsed.command, "cargo install atuin");
|
||||||
assert_eq!(parsed.duration, 0);
|
assert_eq!(parsed.duration, 0);
|
||||||
assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0));
|
assert_eq!(
|
||||||
|
parsed.timestamp,
|
||||||
|
OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let parsed = parse_extended("1613322469:10;cargo install atuin;cargo update", 0);
|
let parsed = parse_extended("1613322469:10;cargo install atuin;cargo update", 0);
|
||||||
|
|
||||||
assert_eq!(parsed.command, "cargo install atuin;cargo update");
|
assert_eq!(parsed.command, "cargo install atuin;cargo update");
|
||||||
assert_eq!(parsed.duration, 10_000_000_000);
|
assert_eq!(parsed.duration, 10_000_000_000);
|
||||||
assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0));
|
assert_eq!(
|
||||||
|
parsed.timestamp,
|
||||||
|
OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let parsed = parse_extended("1613322469:10;cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷", 0);
|
let parsed = parse_extended("1613322469:10;cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷", 0);
|
||||||
|
|
||||||
assert_eq!(parsed.command, "cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷");
|
assert_eq!(parsed.command, "cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷");
|
||||||
assert_eq!(parsed.duration, 10_000_000_000);
|
assert_eq!(parsed.duration, 10_000_000_000);
|
||||||
assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0));
|
assert_eq!(
|
||||||
|
parsed.timestamp,
|
||||||
|
OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let parsed = parse_extended("1613322469:10;cargo install \\n atuin\n", 0);
|
let parsed = parse_extended("1613322469:10;cargo install \\n atuin\n", 0);
|
||||||
|
|
||||||
assert_eq!(parsed.command, "cargo install \\n atuin");
|
assert_eq!(parsed.command, "cargo install \\n atuin");
|
||||||
assert_eq!(parsed.duration, 10_000_000_000);
|
assert_eq!(parsed.duration, 10_000_000_000);
|
||||||
assert_eq!(parsed.timestamp, Utc.timestamp(1_613_322_469, 0));
|
assert_eq!(
|
||||||
|
parsed.timestamp,
|
||||||
|
OffsetDateTime::from_unix_timestamp(1_613_322_469).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -35,10 +35,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use eyre::{eyre, Result};
|
use eyre::{eyre, Result};
|
||||||
use sqlx::{sqlite::SqlitePool, Pool};
|
use sqlx::{sqlite::SqlitePool, Pool};
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
use super::Importer;
|
use super::Importer;
|
||||||
use crate::history::History;
|
use crate::history::History;
|
||||||
|
@ -52,7 +52,7 @@ pub struct HistDbEntryCount {
|
||||||
#[derive(sqlx::FromRow, Debug)]
|
#[derive(sqlx::FromRow, Debug)]
|
||||||
pub struct HistDbEntry {
|
pub struct HistDbEntry {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub start_time: NaiveDateTime,
|
pub start_time: PrimitiveDateTime,
|
||||||
pub host: Vec<u8>,
|
pub host: Vec<u8>,
|
||||||
pub dir: Vec<u8>,
|
pub dir: Vec<u8>,
|
||||||
pub argv: Vec<u8>,
|
pub argv: Vec<u8>,
|
||||||
|
@ -62,7 +62,7 @@ pub struct HistDbEntry {
|
||||||
impl From<HistDbEntry> for History {
|
impl From<HistDbEntry> for History {
|
||||||
fn from(histdb_item: HistDbEntry) -> Self {
|
fn from(histdb_item: HistDbEntry) -> Self {
|
||||||
let imported = History::import()
|
let imported = History::import()
|
||||||
.timestamp(DateTime::from_utc(histdb_item.start_time, Utc))
|
.timestamp(histdb_item.start_time.assume_utc())
|
||||||
.command(
|
.command(
|
||||||
String::from_utf8(histdb_item.argv)
|
String::from_utf8(histdb_item.argv)
|
||||||
.unwrap_or_else(|_e| String::from(""))
|
.unwrap_or_else(|_e| String::from(""))
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
io::prelude::*,
|
io::prelude::*,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use atuin_common::record::HostId;
|
use atuin_common::record::HostId;
|
||||||
use chrono::{prelude::*, Utc};
|
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use config::{
|
use config::{
|
||||||
builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat,
|
builder::DefaultState, Config, ConfigBuilder, Environment, File as ConfigFile, FileFormat,
|
||||||
|
@ -16,6 +16,7 @@ use parse_duration::parse;
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub const HISTORY_PAGE_SIZE: i64 = 100;
|
pub const HISTORY_PAGE_SIZE: i64 = 100;
|
||||||
|
@ -207,21 +208,20 @@ impl Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_current_time(filename: &str) -> Result<()> {
|
fn save_current_time(filename: &str) -> Result<()> {
|
||||||
Settings::save_to_data_dir(filename, Utc::now().to_rfc3339().as_str())?;
|
Settings::save_to_data_dir(
|
||||||
|
filename,
|
||||||
|
OffsetDateTime::now_utc().format(&Rfc3339)?.as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_time_from_file(filename: &str) -> Result<chrono::DateTime<Utc>> {
|
fn load_time_from_file(filename: &str) -> Result<OffsetDateTime> {
|
||||||
let value = Settings::read_from_data_dir(filename);
|
let value = Settings::read_from_data_dir(filename);
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
Some(v) => {
|
Some(v) => Ok(OffsetDateTime::parse(v.as_str(), &Rfc3339)?),
|
||||||
let time = chrono::DateTime::parse_from_rfc3339(v.as_str())?;
|
None => Ok(OffsetDateTime::UNIX_EPOCH),
|
||||||
|
|
||||||
Ok(time.with_timezone(&Utc))
|
|
||||||
}
|
|
||||||
None => Ok(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,11 +233,11 @@ impl Settings {
|
||||||
Settings::save_current_time(LAST_VERSION_CHECK_FILENAME)
|
Settings::save_current_time(LAST_VERSION_CHECK_FILENAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_sync() -> Result<chrono::DateTime<Utc>> {
|
pub fn last_sync() -> Result<OffsetDateTime> {
|
||||||
Settings::load_time_from_file(LAST_SYNC_FILENAME)
|
Settings::load_time_from_file(LAST_SYNC_FILENAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_version_check() -> Result<chrono::DateTime<Utc>> {
|
pub fn last_version_check() -> Result<OffsetDateTime> {
|
||||||
Settings::load_time_from_file(LAST_VERSION_CHECK_FILENAME)
|
Settings::load_time_from_file(LAST_VERSION_CHECK_FILENAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,8 +265,8 @@ impl Settings {
|
||||||
|
|
||||||
match parse(self.sync_frequency.as_str()) {
|
match parse(self.sync_frequency.as_str()) {
|
||||||
Ok(d) => {
|
Ok(d) => {
|
||||||
let d = chrono::Duration::from_std(d).unwrap();
|
let d = time::Duration::try_from(d).unwrap();
|
||||||
Ok(Utc::now() - Settings::last_sync()? >= d)
|
Ok(OffsetDateTime::now_utc() - Settings::last_sync()? >= d)
|
||||||
}
|
}
|
||||||
Err(e) => Err(eyre!("failed to check sync: {}", e)),
|
Err(e) => Err(eyre!("failed to check sync: {}", e)),
|
||||||
}
|
}
|
||||||
|
@ -274,10 +274,10 @@ impl Settings {
|
||||||
|
|
||||||
fn needs_update_check(&self) -> Result<bool> {
|
fn needs_update_check(&self) -> Result<bool> {
|
||||||
let last_check = Settings::last_version_check()?;
|
let last_check = Settings::last_version_check()?;
|
||||||
let diff = Utc::now() - last_check;
|
let diff = OffsetDateTime::now_utc() - last_check;
|
||||||
|
|
||||||
// Check a max of once per hour
|
// Check a max of once per hour
|
||||||
Ok(diff.num_hours() >= 1)
|
Ok(diff.whole_hours() >= 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn latest_version(&self) -> Result<Version> {
|
async fn latest_version(&self) -> Result<Version> {
|
||||||
|
|
|
@ -2,11 +2,11 @@ use std::collections::HashSet;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use chrono::prelude::*;
|
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
|
|
||||||
use atuin_common::api::AddHistoryRequest;
|
use atuin_common::api::AddHistoryRequest;
|
||||||
use crypto_secretbox::Key;
|
use crypto_secretbox::Key;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api_client,
|
api_client,
|
||||||
|
@ -52,12 +52,12 @@ async fn sync_download(
|
||||||
let mut local_count = initial_local;
|
let mut local_count = initial_local;
|
||||||
|
|
||||||
let mut last_sync = if force {
|
let mut last_sync = if force {
|
||||||
Utc.timestamp_millis(0)
|
OffsetDateTime::UNIX_EPOCH
|
||||||
} else {
|
} else {
|
||||||
Settings::last_sync()?
|
Settings::last_sync()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut last_timestamp = Utc.timestamp_millis(0);
|
let mut last_timestamp = OffsetDateTime::UNIX_EPOCH;
|
||||||
|
|
||||||
let host = if force { Some(String::from("")) } else { None };
|
let host = if force { Some(String::from("")) } else { None };
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ async fn sync_download(
|
||||||
.map(|h| decrypt(h, key).expect("failed to decrypt history! check your key"))
|
.map(|h| decrypt(h, key).expect("failed to decrypt history! check your key"))
|
||||||
.map(|mut h| {
|
.map(|mut h| {
|
||||||
if remote_deleted.contains(h.id.as_str()) {
|
if remote_deleted.contains(h.id.as_str()) {
|
||||||
h.deleted_at = Some(chrono::Utc::now());
|
h.deleted_at = Some(time::OffsetDateTime::now_utc());
|
||||||
h.command = String::from("");
|
h.command = String::from("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,8 +99,8 @@ async fn sync_download(
|
||||||
// be "lost" between syncs. In this case we need to rewind the sync
|
// be "lost" between syncs. In this case we need to rewind the sync
|
||||||
// timestamps
|
// timestamps
|
||||||
if page_last == last_timestamp {
|
if page_last == last_timestamp {
|
||||||
last_timestamp = Utc.timestamp_millis(0);
|
last_timestamp = OffsetDateTime::UNIX_EPOCH;
|
||||||
last_sync -= chrono::Duration::hours(1);
|
last_sync -= time::Duration::hours(1);
|
||||||
} else {
|
} else {
|
||||||
last_timestamp = page_last;
|
last_timestamp = page_last;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ async fn sync_upload(
|
||||||
debug!("remote has {}, we have {}", remote_count, local_count);
|
debug!("remote has {}, we have {}", remote_count, local_count);
|
||||||
|
|
||||||
// first just try the most recent set
|
// first just try the most recent set
|
||||||
let mut cursor = Utc::now();
|
let mut cursor = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
while local_count > remote_count {
|
while local_count > remote_count {
|
||||||
let last = db.before(cursor, remote_status.page_size).await?;
|
let last = db.before(cursor, remote_status.page_size).await?;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "atuin-common"
|
name = "atuin-common"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
description = "common library for atuin"
|
description = "common library for atuin"
|
||||||
|
|
||||||
|
rust-version = { workspace = true }
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
@ -12,7 +13,7 @@ repository = { workspace = true }
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { workspace = true }
|
time = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::Utc;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct UserResponse {
|
pub struct UserResponse {
|
||||||
|
@ -36,7 +36,8 @@ pub struct LoginResponse {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct AddHistoryRequest {
|
pub struct AddHistoryRequest {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub timestamp: chrono::DateTime<Utc>,
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
pub timestamp: OffsetDateTime,
|
||||||
pub data: String,
|
pub data: String,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
}
|
}
|
||||||
|
@ -48,8 +49,10 @@ pub struct CountResponse {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SyncHistoryRequest {
|
pub struct SyncHistoryRequest {
|
||||||
pub sync_ts: chrono::DateTime<chrono::FixedOffset>,
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
pub history_ts: chrono::DateTime<chrono::FixedOffset>,
|
pub sync_ts: OffsetDateTime,
|
||||||
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
pub history_ts: OffsetDateTime,
|
||||||
pub host: String,
|
pub host: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub struct Record<Data> {
|
||||||
pub parent: Option<RecordId>,
|
pub parent: Option<RecordId>,
|
||||||
|
|
||||||
/// The creation time in nanoseconds since unix epoch
|
/// The creation time in nanoseconds since unix epoch
|
||||||
#[builder(default = chrono::Utc::now().timestamp_nanos() as u64)]
|
#[builder(default = time::OffsetDateTime::now_utc().unix_timestamp_nanos() as u64)]
|
||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
|
|
||||||
/// The version the data in the entry conforms to
|
/// The version the data in the entry conforms to
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use chrono::{Months, NaiveDate};
|
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -37,7 +36,10 @@ const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> U
|
||||||
|
|
||||||
pub fn uuid_v7() -> Uuid {
|
pub fn uuid_v7() -> Uuid {
|
||||||
let bytes = random_bytes();
|
let bytes = random_bytes();
|
||||||
let now: u64 = chrono::Utc::now().timestamp_millis() as u64;
|
let now: u64 = u64::try_from(
|
||||||
|
time::OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000_000,
|
||||||
|
)
|
||||||
|
.expect("Either you're in the past (1970) - or your in the far future (2554). Good for you");
|
||||||
|
|
||||||
encode_unix_timestamp_millis(now, &bytes)
|
encode_unix_timestamp_millis(now, &bytes)
|
||||||
}
|
}
|
||||||
|
@ -111,18 +113,10 @@ pub fn get_current_dir() -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_days_from_month(year: i32, month: u32) -> i64 {
|
|
||||||
let Some(start) = NaiveDate::from_ymd_opt(year, month, 1) else {
|
|
||||||
return 30;
|
|
||||||
};
|
|
||||||
let Some(end) = start.checked_add_months(Months::new(1)) else {
|
|
||||||
return 30;
|
|
||||||
};
|
|
||||||
end.signed_duration_since(start).num_days()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use time::Month;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
@ -170,21 +164,21 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn days_from_month() {
|
fn days_from_month() {
|
||||||
assert_eq!(get_days_from_month(2023, 1), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::January), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 2), 28);
|
assert_eq!(time::util::days_in_year_month(2023, Month::February), 28);
|
||||||
assert_eq!(get_days_from_month(2023, 3), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::March), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 4), 30);
|
assert_eq!(time::util::days_in_year_month(2023, Month::April), 30);
|
||||||
assert_eq!(get_days_from_month(2023, 5), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::May), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 6), 30);
|
assert_eq!(time::util::days_in_year_month(2023, Month::June), 30);
|
||||||
assert_eq!(get_days_from_month(2023, 7), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::July), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 8), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::August), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 9), 30);
|
assert_eq!(time::util::days_in_year_month(2023, Month::September), 30);
|
||||||
assert_eq!(get_days_from_month(2023, 10), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::October), 31);
|
||||||
assert_eq!(get_days_from_month(2023, 11), 30);
|
assert_eq!(time::util::days_in_year_month(2023, Month::November), 30);
|
||||||
assert_eq!(get_days_from_month(2023, 12), 31);
|
assert_eq!(time::util::days_in_year_month(2023, Month::December), 31);
|
||||||
|
|
||||||
// leap years
|
// leap years
|
||||||
assert_eq!(get_days_from_month(2024, 2), 29);
|
assert_eq!(time::util::days_in_year_month(2024, Month::February), 29);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -13,9 +13,8 @@ repository = { workspace = true }
|
||||||
atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
||||||
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
chrono = { workspace = true }
|
time = { workspace = true }
|
||||||
eyre = { workspace = true }
|
eyre = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
chronoutil = "0.2.3"
|
|
||||||
|
|
|
@ -13,13 +13,9 @@ use self::{
|
||||||
models::{History, NewHistory, NewSession, NewUser, Session, User},
|
models::{History, NewHistory, NewSession, NewUser, Session, User},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use atuin_common::{
|
use atuin_common::record::{EncryptedData, HostId, Record, RecordId, RecordIndex};
|
||||||
record::{EncryptedData, HostId, Record, RecordId, RecordIndex},
|
|
||||||
utils::get_days_from_month,
|
|
||||||
};
|
|
||||||
use chrono::{Datelike, TimeZone};
|
|
||||||
use chronoutil::RelativeDuration;
|
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -34,6 +30,12 @@ impl Display for DbError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: std::error::Error + Into<time::error::Error>> From<T> for DbError {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
DbError::Other(value.into().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::error::Error for DbError {}
|
impl std::error::Error for DbError {}
|
||||||
|
|
||||||
pub type DbResult<T> = Result<T, DbError>;
|
pub type DbResult<T> = Result<T, DbError>;
|
||||||
|
@ -75,15 +77,15 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
async fn count_history_range(
|
async fn count_history_range(
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
user: &User,
|
||||||
start: chrono::NaiveDateTime,
|
start: PrimitiveDateTime,
|
||||||
end: chrono::NaiveDateTime,
|
end: PrimitiveDateTime,
|
||||||
) -> DbResult<i64>;
|
) -> DbResult<i64>;
|
||||||
|
|
||||||
async fn list_history(
|
async fn list_history(
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
user: &User,
|
||||||
created_after: chrono::NaiveDateTime,
|
created_after: OffsetDateTime,
|
||||||
since: chrono::NaiveDateTime,
|
since: OffsetDateTime,
|
||||||
host: &str,
|
host: &str,
|
||||||
page_size: i64,
|
page_size: i64,
|
||||||
) -> DbResult<Vec<History>>;
|
) -> DbResult<Vec<History>>;
|
||||||
|
@ -95,53 +97,51 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
/// Count the history for a given year
|
/// Count the history for a given year
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn count_history_year(&self, user: &User, year: i32) -> DbResult<i64> {
|
async fn count_history_year(&self, user: &User, year: i32) -> DbResult<i64> {
|
||||||
let start = chrono::Utc.ymd(year, 1, 1).and_hms_nano(0, 0, 0, 0);
|
let start = Date::from_calendar_date(year, time::Month::January, 1)?;
|
||||||
let end = start + RelativeDuration::years(1);
|
let end = Date::from_calendar_date(year + 1, time::Month::January, 1)?;
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.count_history_range(user, start.naive_utc(), end.naive_utc())
|
.count_history_range(
|
||||||
|
user,
|
||||||
|
start.with_time(Time::MIDNIGHT),
|
||||||
|
end.with_time(Time::MIDNIGHT),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count the history for a given month
|
/// Count the history for a given month
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn count_history_month(&self, user: &User, month: chrono::NaiveDate) -> DbResult<i64> {
|
async fn count_history_month(&self, user: &User, year: i32, month: Month) -> DbResult<i64> {
|
||||||
let start = chrono::Utc
|
let start = Date::from_calendar_date(year, month, 1)?;
|
||||||
.ymd(month.year(), month.month(), 1)
|
let days = time::util::days_in_year_month(year, month);
|
||||||
.and_hms_nano(0, 0, 0, 0);
|
let end = start + Duration::days(days as i64);
|
||||||
|
|
||||||
// ofc...
|
|
||||||
let end = if month.month() < 12 {
|
|
||||||
chrono::Utc
|
|
||||||
.ymd(month.year(), month.month() + 1, 1)
|
|
||||||
.and_hms_nano(0, 0, 0, 0)
|
|
||||||
} else {
|
|
||||||
chrono::Utc
|
|
||||||
.ymd(month.year() + 1, 1, 1)
|
|
||||||
.and_hms_nano(0, 0, 0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("start: {}, end: {}", start, end);
|
tracing::debug!("start: {}, end: {}", start, end);
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.count_history_range(user, start.naive_utc(), end.naive_utc())
|
.count_history_range(
|
||||||
|
user,
|
||||||
|
start.with_time(Time::MIDNIGHT),
|
||||||
|
end.with_time(Time::MIDNIGHT),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count the history for a given day
|
/// Count the history for a given day
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn count_history_day(&self, user: &User, day: chrono::NaiveDate) -> DbResult<i64> {
|
async fn count_history_day(&self, user: &User, day: Date) -> DbResult<i64> {
|
||||||
let start = chrono::Utc
|
let end = day
|
||||||
.ymd(day.year(), day.month(), day.day())
|
.next_day()
|
||||||
.and_hms_nano(0, 0, 0, 0);
|
.ok_or_else(|| DbError::Other(eyre::eyre!("no next day?")))?;
|
||||||
let end = chrono::Utc
|
|
||||||
.ymd(day.year(), day.month(), day.day() + 1)
|
|
||||||
.and_hms_nano(0, 0, 0, 0);
|
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
.count_history_range(user, start.naive_utc(), end.naive_utc())
|
.count_history_range(
|
||||||
|
user,
|
||||||
|
day.with_time(Time::MIDNIGHT),
|
||||||
|
end.with_time(Time::MIDNIGHT),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
user: &User,
|
user: &User,
|
||||||
period: TimePeriod,
|
period: TimePeriod,
|
||||||
year: u64,
|
year: u64,
|
||||||
month: u64,
|
month: Month,
|
||||||
) -> DbResult<HashMap<u64, TimePeriodInfo>> {
|
) -> DbResult<HashMap<u64, TimePeriodInfo>> {
|
||||||
// TODO: Support different timezones. Right now we assume UTC and
|
// TODO: Support different timezones. Right now we assume UTC and
|
||||||
// everything is stored as such. But it _should_ be possible to
|
// everything is stored as such. But it _should_ be possible to
|
||||||
|
@ -164,7 +164,7 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
// First we need to work out how far back to calculate. Get the
|
// First we need to work out how far back to calculate. Get the
|
||||||
// oldest history item
|
// oldest history item
|
||||||
let oldest = self.oldest_history(user).await?.timestamp.year();
|
let oldest = self.oldest_history(user).await?.timestamp.year();
|
||||||
let current_year = chrono::Utc::now().year();
|
let current_year = OffsetDateTime::now_utc().year();
|
||||||
|
|
||||||
// All the years we need to get data for
|
// All the years we need to get data for
|
||||||
// The upper bound is exclusive, so include current +1
|
// The upper bound is exclusive, so include current +1
|
||||||
|
@ -188,13 +188,10 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
TimePeriod::MONTH => {
|
TimePeriod::MONTH => {
|
||||||
let mut ret = HashMap::new();
|
let mut ret = HashMap::new();
|
||||||
|
|
||||||
for month in 1..13 {
|
let months =
|
||||||
let count = self
|
std::iter::successors(Some(Month::January), |m| Some(m.next())).take(12);
|
||||||
.count_history_month(
|
for month in months {
|
||||||
user,
|
let count = self.count_history_month(user, year as i32, month).await?;
|
||||||
chrono::Utc.ymd(year as i32, month, 1).naive_utc(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
ret.insert(
|
ret.insert(
|
||||||
month as u64,
|
month as u64,
|
||||||
|
@ -211,14 +208,9 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
||||||
TimePeriod::DAY => {
|
TimePeriod::DAY => {
|
||||||
let mut ret = HashMap::new();
|
let mut ret = HashMap::new();
|
||||||
|
|
||||||
for day in 1..get_days_from_month(year as i32, month as u32) {
|
for day in 1..time::util::days_in_year_month(year as i32, month) {
|
||||||
let count = self
|
let count = self
|
||||||
.count_history_day(
|
.count_history_day(user, Date::from_calendar_date(year as i32, month, day)?)
|
||||||
user,
|
|
||||||
chrono::Utc
|
|
||||||
.ymd(year as i32, month as u32, day as u32)
|
|
||||||
.naive_utc(),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
ret.insert(
|
ret.insert(
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
use chrono::prelude::*;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
pub struct History {
|
pub struct History {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub client_id: String, // a client generated ID
|
pub client_id: String, // a client generated ID
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub timestamp: NaiveDateTime,
|
pub timestamp: OffsetDateTime,
|
||||||
|
|
||||||
/// All the data we have about this command, encrypted.
|
/// All the data we have about this command, encrypted.
|
||||||
///
|
///
|
||||||
/// Currently this is an encrypted msgpack object, but this may change in the future.
|
/// Currently this is an encrypted msgpack object, but this may change in the future.
|
||||||
pub data: String,
|
pub data: String,
|
||||||
|
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewHistory {
|
pub struct NewHistory {
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub timestamp: chrono::NaiveDateTime,
|
pub timestamp: OffsetDateTime,
|
||||||
|
|
||||||
/// All the data we have about this command, encrypted.
|
/// All the data we have about this command, encrypted.
|
||||||
///
|
///
|
||||||
|
|
|
@ -14,7 +14,7 @@ atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
||||||
atuin-server-database = { path = "../atuin-server-database", version = "16.0.0" }
|
atuin-server-database = { path = "../atuin-server-database", version = "16.0.0" }
|
||||||
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
chrono = { workspace = true }
|
time = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
sqlx = { workspace = true }
|
sqlx = { workspace = true }
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
|
|
|
@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
|
|
||||||
|
use time::{OffsetDateTime, PrimitiveDateTime};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use wrappers::{DbHistory, DbRecord, DbSession, DbUser};
|
use wrappers::{DbHistory, DbRecord, DbSession, DbUser};
|
||||||
|
|
||||||
|
@ -139,7 +140,7 @@ impl Database for Postgres {
|
||||||
)
|
)
|
||||||
.bind(user.id)
|
.bind(user.id)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.bind(chrono::Utc::now().naive_utc())
|
.bind(OffsetDateTime::now_utc())
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(fix_error)?;
|
.map_err(fix_error)?;
|
||||||
|
@ -175,8 +176,8 @@ impl Database for Postgres {
|
||||||
async fn count_history_range(
|
async fn count_history_range(
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
user: &User,
|
||||||
start: chrono::NaiveDateTime,
|
start: PrimitiveDateTime,
|
||||||
end: chrono::NaiveDateTime,
|
end: PrimitiveDateTime,
|
||||||
) -> DbResult<i64> {
|
) -> DbResult<i64> {
|
||||||
let res: (i64,) = sqlx::query_as(
|
let res: (i64,) = sqlx::query_as(
|
||||||
"select count(1) from history
|
"select count(1) from history
|
||||||
|
@ -198,8 +199,8 @@ impl Database for Postgres {
|
||||||
async fn list_history(
|
async fn list_history(
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
user: &User,
|
||||||
created_after: chrono::NaiveDateTime,
|
created_after: OffsetDateTime,
|
||||||
since: chrono::NaiveDateTime,
|
since: OffsetDateTime,
|
||||||
host: &str,
|
host: &str,
|
||||||
page_size: i64,
|
page_size: i64,
|
||||||
) -> DbResult<Vec<History>> {
|
) -> DbResult<Vec<History>> {
|
||||||
|
|
|
@ -3,6 +3,7 @@ name = "atuin-server"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "server library for atuin"
|
description = "server library for atuin"
|
||||||
|
|
||||||
|
rust-version = { workspace = true }
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
@ -14,7 +15,7 @@ atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
||||||
atuin-server-database = { path = "../atuin-server-database", version = "16.0.0" }
|
atuin-server-database = { path = "../atuin-server-database", version = "16.0.0" }
|
||||||
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
chrono = { workspace = true }
|
time = { workspace = true }
|
||||||
eyre = { workspace = true }
|
eyre = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
config = { workspace = true }
|
config = { workspace = true }
|
||||||
|
@ -27,7 +28,6 @@ async-trait = { workspace = true }
|
||||||
axum = "0.6.4"
|
axum = "0.6.4"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
chronoutil = "0.2.3"
|
|
||||||
tower = "0.4"
|
tower = "0.4"
|
||||||
tower-http = { version = "0.3", features = ["trace"] }
|
tower-http = { version = "0.3", features = ["trace"] }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, convert::TryFrom};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, Query, State},
|
extract::{Path, Query, State},
|
||||||
|
@ -6,6 +6,7 @@ use axum::{
|
||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use time::Month;
|
||||||
use tracing::{debug, error, instrument};
|
use tracing::{debug, error, instrument};
|
||||||
|
|
||||||
use super::{ErrorResponse, ErrorResponseStatus, RespExt};
|
use super::{ErrorResponse, ErrorResponseStatus, RespExt};
|
||||||
|
@ -63,16 +64,10 @@ pub async fn list<DB: Database>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let history = db
|
let history = db
|
||||||
.list_history(
|
.list_history(&user, req.sync_ts, req.history_ts, &req.host, page_size)
|
||||||
&user,
|
|
||||||
req.sync_ts.naive_utc(),
|
|
||||||
req.history_ts.naive_utc(),
|
|
||||||
&req.host,
|
|
||||||
page_size,
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if req.sync_ts.timestamp_nanos() < 0 || req.history_ts.timestamp_nanos() < 0 {
|
if req.sync_ts.unix_timestamp_nanos() < 0 || req.history_ts.unix_timestamp_nanos() < 0 {
|
||||||
error!("client asked for history from < epoch 0");
|
error!("client asked for history from < epoch 0");
|
||||||
return Err(
|
return Err(
|
||||||
ErrorResponse::reply("asked for history from before epoch 0")
|
ErrorResponse::reply("asked for history from before epoch 0")
|
||||||
|
@ -139,7 +134,7 @@ pub async fn add<DB: Database>(
|
||||||
client_id: h.id,
|
client_id: h.id,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
hostname: h.hostname,
|
hostname: h.hostname,
|
||||||
timestamp: h.timestamp.naive_utc(),
|
timestamp: h.timestamp,
|
||||||
data: h.data,
|
data: h.data,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -182,11 +177,17 @@ pub async fn calendar<DB: Database>(
|
||||||
|
|
||||||
let year = params.get("year").unwrap_or(&0);
|
let year = params.get("year").unwrap_or(&0);
|
||||||
let month = params.get("month").unwrap_or(&1);
|
let month = params.get("month").unwrap_or(&1);
|
||||||
|
let month = Month::try_from(*month as u8).map_err(|e| ErrorResponseStatus {
|
||||||
|
error: ErrorResponse {
|
||||||
|
reason: e.to_string().into(),
|
||||||
|
},
|
||||||
|
status: http::StatusCode::BAD_REQUEST,
|
||||||
|
})?;
|
||||||
|
|
||||||
let db = &state.0.database;
|
let db = &state.0.database;
|
||||||
let focus = match focus {
|
let focus = match focus {
|
||||||
"year" => db
|
"year" => db
|
||||||
.calendar(&user, TimePeriod::YEAR, *year, *month)
|
.calendar(&user, TimePeriod::YEAR, *year, month)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ErrorResponse::reply("failed to query calendar")
|
ErrorResponse::reply("failed to query calendar")
|
||||||
|
@ -194,7 +195,7 @@ pub async fn calendar<DB: Database>(
|
||||||
}),
|
}),
|
||||||
|
|
||||||
"month" => db
|
"month" => db
|
||||||
.calendar(&user, TimePeriod::MONTH, *year, *month)
|
.calendar(&user, TimePeriod::MONTH, *year, month)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ErrorResponse::reply("failed to query calendar")
|
ErrorResponse::reply("failed to query calendar")
|
||||||
|
@ -202,7 +203,7 @@ pub async fn calendar<DB: Database>(
|
||||||
}),
|
}),
|
||||||
|
|
||||||
"day" => db
|
"day" => db
|
||||||
.calendar(&user, TimePeriod::DAY, *year, *month)
|
.calendar(&user, TimePeriod::DAY, *year, month)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ErrorResponse::reply("failed to query calendar")
|
ErrorResponse::reply("failed to query calendar")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "atuin"
|
name = "atuin"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.59"
|
|
||||||
description = "atuin - magical shell history"
|
description = "atuin - magical shell history"
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
|
|
||||||
|
rust-version = { workspace = true }
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
@ -46,7 +46,7 @@ atuin-common = { path = "../atuin-common", version = "16.0.0" }
|
||||||
|
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
time = { workspace = true }
|
||||||
eyre = { workspace = true }
|
eyre = { workspace = true }
|
||||||
directories = { workspace = true }
|
directories = { workspace = true }
|
||||||
indicatif = "0.17.5"
|
indicatif = "0.17.5"
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
|
|
||||||
use atuin_common::utils;
|
use atuin_common::utils;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use eyre::Result;
|
use eyre::{Context, Result};
|
||||||
use runtime_format::{FormatKey, FormatKeyError, ParseSegment, ParsedFmt};
|
use runtime_format::{FormatKey, FormatKeyError, ParseSegment, ParsedFmt};
|
||||||
|
|
||||||
use atuin_client::{
|
use atuin_client::{
|
||||||
|
@ -19,8 +19,8 @@ use atuin_client::{
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
use atuin_client::sync;
|
use atuin_client::sync;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use time::{macros::format_description, OffsetDateTime};
|
||||||
|
|
||||||
use super::search::format_duration;
|
|
||||||
use super::search::format_duration_into;
|
use super::search::format_duration_into;
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
@ -141,6 +141,9 @@ pub fn print_list(h: &[History], list_mode: ListMode, format: Option<&str>) {
|
||||||
/// type wrapper around `History` so we can implement traits
|
/// type wrapper around `History` so we can implement traits
|
||||||
struct FmtHistory<'a>(&'a History);
|
struct FmtHistory<'a>(&'a History);
|
||||||
|
|
||||||
|
static TIME_FMT: &[time::format_description::FormatItem<'static>] =
|
||||||
|
format_description!("[year]-[month]-[day] [hour repr:24]:[minute]:[second]");
|
||||||
|
|
||||||
/// defines how to format the history
|
/// defines how to format the history
|
||||||
impl FormatKey for FmtHistory<'_> {
|
impl FormatKey for FmtHistory<'_> {
|
||||||
#[allow(clippy::cast_sign_loss)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
@ -153,11 +156,17 @@ impl FormatKey for FmtHistory<'_> {
|
||||||
let dur = Duration::from_nanos(std::cmp::max(self.0.duration, 0) as u64);
|
let dur = Duration::from_nanos(std::cmp::max(self.0.duration, 0) as u64);
|
||||||
format_duration_into(dur, f)?;
|
format_duration_into(dur, f)?;
|
||||||
}
|
}
|
||||||
"time" => self.0.timestamp.format("%Y-%m-%d %H:%M:%S").fmt(f)?,
|
"time" => {
|
||||||
|
self.0
|
||||||
|
.timestamp
|
||||||
|
.format(TIME_FMT)
|
||||||
|
.map_err(|_| fmt::Error)?
|
||||||
|
.fmt(f)?;
|
||||||
|
}
|
||||||
"relativetime" => {
|
"relativetime" => {
|
||||||
let since = chrono::Utc::now() - self.0.timestamp;
|
let since = OffsetDateTime::now_utc() - self.0.timestamp;
|
||||||
let time = format_duration(since.to_std().unwrap_or_default());
|
let d = Duration::try_from(since).unwrap_or_default();
|
||||||
f.write_str(&time)?;
|
format_duration_into(d, f)?;
|
||||||
}
|
}
|
||||||
"host" => f.write_str(
|
"host" => f.write_str(
|
||||||
self.0
|
self.0
|
||||||
|
@ -184,6 +193,7 @@ fn parse_fmt(format: &str) -> ParsedFmt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cmd {
|
impl Cmd {
|
||||||
|
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
|
||||||
async fn handle_start(
|
async fn handle_start(
|
||||||
db: &mut impl Database,
|
db: &mut impl Database,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
|
@ -196,7 +206,7 @@ impl Cmd {
|
||||||
let cwd = utils::get_current_dir();
|
let cwd = utils::get_current_dir();
|
||||||
|
|
||||||
let h: History = History::capture()
|
let h: History = History::capture()
|
||||||
.timestamp(chrono::Utc::now())
|
.timestamp(OffsetDateTime::now_utc())
|
||||||
.command(command)
|
.command(command)
|
||||||
.cwd(cwd)
|
.cwd(cwd)
|
||||||
.build()
|
.build()
|
||||||
|
@ -233,7 +243,8 @@ impl Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
h.exit = exit;
|
h.exit = exit;
|
||||||
h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
|
h.duration = i64::try_from((OffsetDateTime::now_utc() - h.timestamp).whole_nanoseconds())
|
||||||
|
.context("command took over 292 years")?;
|
||||||
|
|
||||||
db.update(&h).await?;
|
db.update(&h).await?;
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,7 @@ impl Cmd {
|
||||||
|
|
||||||
// This is supposed to more-or-less mirror the command line version, so ofc
|
// This is supposed to more-or-less mirror the command line version, so ofc
|
||||||
// it is going to have a lot of args
|
// it is going to have a lot of args
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments, clippy::cast_possible_truncation)]
|
||||||
async fn run_non_interactive(
|
async fn run_non_interactive(
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
filter_options: OptFilters,
|
filter_options: OptFilters,
|
||||||
|
|
|
@ -2,10 +2,10 @@ use std::path::Path;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use atuin_client::{database::Database, history::History, settings::FilterMode};
|
use atuin_client::{database::Database, history::History, settings::FilterMode};
|
||||||
use chrono::Utc;
|
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use time::OffsetDateTime;
|
||||||
use tokio::task::yield_now;
|
use tokio::task::yield_now;
|
||||||
|
|
||||||
use super::{SearchEngine, SearchState};
|
use super::{SearchEngine, SearchState};
|
||||||
|
@ -47,7 +47,7 @@ async fn fuzzy_search(
|
||||||
let mut set = Vec::with_capacity(200);
|
let mut set = Vec::with_capacity(200);
|
||||||
let mut ranks = Vec::with_capacity(200);
|
let mut ranks = Vec::with_capacity(200);
|
||||||
let query = state.input.as_str();
|
let query = state.input.as_str();
|
||||||
let now = Utc::now();
|
let now = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
for (i, (history, count)) in all_history.iter().enumerate() {
|
for (i, (history, count)) in all_history.iter().enumerate() {
|
||||||
if i % 256 == 0 {
|
if i % 256 == 0 {
|
||||||
|
@ -78,7 +78,7 @@ async fn fuzzy_search(
|
||||||
if let Some((score, indices)) = engine.fuzzy_indices(&history.command, query) {
|
if let Some((score, indices)) = engine.fuzzy_indices(&history.command, query) {
|
||||||
let begin = indices.first().copied().unwrap_or_default();
|
let begin = indices.first().copied().unwrap_or_default();
|
||||||
|
|
||||||
let mut duration = ((now - history.timestamp).num_seconds() as f64).log2();
|
let mut duration = (now - history.timestamp).as_seconds_f64().log2();
|
||||||
if !duration.is_finite() || duration <= 1.0 {
|
if !duration.is_finite() || duration <= 1.0 {
|
||||||
duration = 1.0;
|
duration = 1.0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use ratatui::{
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
widgets::{Block, StatefulWidget, Widget},
|
widgets::{Block, StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::format_duration;
|
use super::format_duration;
|
||||||
|
|
||||||
|
@ -151,8 +152,8 @@ impl DrawState<'_> {
|
||||||
// would fail.
|
// would fail.
|
||||||
// If the timestamp would otherwise be in the future, display
|
// If the timestamp would otherwise be in the future, display
|
||||||
// the time since as 0.
|
// the time since as 0.
|
||||||
let since = chrono::Utc::now() - h.timestamp;
|
let since = OffsetDateTime::now_utc() - h.timestamp;
|
||||||
let time = format_duration(since.to_std().unwrap_or_default());
|
let time = format_duration(since.try_into().unwrap_or_default());
|
||||||
|
|
||||||
// pad the time a little bit before we write. this aligns things nicely
|
// pad the time a little bit before we write. this aligns things nicely
|
||||||
self.x = PREFIX_LENGTH - 4 - time.len() as u16;
|
self.x = PREFIX_LENGTH - 4 - time.len() as u16;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use chrono::{prelude::*, Duration};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::style::{Color, ResetColor, SetAttribute, SetForegroundColor};
|
use crossterm::style::{Color, ResetColor, SetAttribute, SetForegroundColor};
|
||||||
use eyre::{bail, Result};
|
use eyre::{bail, Result};
|
||||||
|
@ -11,6 +10,7 @@ use atuin_client::{
|
||||||
history::History,
|
history::History,
|
||||||
settings::{FilterMode, Settings},
|
settings::{FilterMode, Settings},
|
||||||
};
|
};
|
||||||
|
use time::{Duration, OffsetDateTime, Time};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(infer_subcommands = true)]
|
#[command(infer_subcommands = true)]
|
||||||
|
@ -84,25 +84,29 @@ impl Cmd {
|
||||||
let history = if words.as_str() == "all" {
|
let history = if words.as_str() == "all" {
|
||||||
db.list(FilterMode::Global, &context, None, false).await?
|
db.list(FilterMode::Global, &context, None, false).await?
|
||||||
} else if words.trim() == "today" {
|
} else if words.trim() == "today" {
|
||||||
let start = Local::now().date().and_hms(0, 0, 0);
|
let start = OffsetDateTime::now_local()?.replace_time(Time::MIDNIGHT);
|
||||||
let end = start + Duration::days(1);
|
let end = start + Duration::days(1);
|
||||||
db.range(start.into(), end.into()).await?
|
db.range(start, end).await?
|
||||||
} else if words.trim() == "month" {
|
} else if words.trim() == "month" {
|
||||||
let end = Local::now().date().and_hms(0, 0, 0);
|
let end = OffsetDateTime::now_local()?.replace_time(Time::MIDNIGHT);
|
||||||
let start = end - Duration::days(31);
|
let start = end - Duration::days(31);
|
||||||
db.range(start.into(), end.into()).await?
|
db.range(start, end).await?
|
||||||
} else if words.trim() == "week" {
|
} else if words.trim() == "week" {
|
||||||
let end = Local::now().date().and_hms(0, 0, 0);
|
let end = OffsetDateTime::now_local()?.replace_time(Time::MIDNIGHT);
|
||||||
let start = end - Duration::days(7);
|
let start = end - Duration::days(7);
|
||||||
db.range(start.into(), end.into()).await?
|
db.range(start, end).await?
|
||||||
} else if words.trim() == "year" {
|
} else if words.trim() == "year" {
|
||||||
let end = Local::now().date().and_hms(0, 0, 0);
|
let end = OffsetDateTime::now_local()?.replace_time(Time::MIDNIGHT);
|
||||||
let start = end - Duration::days(365);
|
let start = end - Duration::days(365);
|
||||||
db.range(start.into(), end.into()).await?
|
db.range(start, end).await?
|
||||||
} else {
|
} else {
|
||||||
let start = parse_date_string(&words, Local::now(), settings.dialect.into())?;
|
let start = parse_date_string(
|
||||||
|
&words,
|
||||||
|
OffsetDateTime::now_local()?,
|
||||||
|
settings.dialect.into(),
|
||||||
|
)?;
|
||||||
let end = start + Duration::days(1);
|
let end = start + Duration::days(1);
|
||||||
db.range(start.into(), end.into()).await?
|
db.range(start, end).await?
|
||||||
};
|
};
|
||||||
compute_stats(&history, self.count)?;
|
compute_stats(&history, self.count)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -26,8 +26,6 @@ unmaintained = "warn"
|
||||||
yanked = "warn"
|
yanked = "warn"
|
||||||
notice = "warn"
|
notice = "warn"
|
||||||
ignore = [
|
ignore = [
|
||||||
# time 0.1 - code path not taken
|
|
||||||
"RUSTSEC-2020-0071",
|
|
||||||
# potential to misuse ed25519-dalek 1.0
|
# potential to misuse ed25519-dalek 1.0
|
||||||
# used by rusty-paseto. not in a vulnerable way
|
# used by rusty-paseto. not in a vulnerable way
|
||||||
# and we don't even use paseto public key crypto so we don't use this
|
# and we don't even use paseto public key crypto so we don't use this
|
||||||
|
|
Loading…
Reference in a new issue