diff --git a/Cargo.lock b/Cargo.lock index cbfb9d3..1f6759c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -82,12 +91,104 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -103,6 +204,18 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -121,6 +234,18 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cc" version = "1.2.16" @@ -196,18 +321,95 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console-api" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "hyper-util", + "prost", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "file_monitor" version = "0.0.0" dependencies = [ "chrono", "clap", + "console-subscriber", + "home", "notify", "tokio", ] @@ -224,6 +426,22 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -233,18 +451,226 @@ dependencies = [ "libc", ] +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.8.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -268,6 +694,26 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + [[package]] name = "inotify" version = "0.11.0" @@ -294,6 +740,21 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.77" @@ -324,6 +785,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.171" @@ -347,12 +814,39 @@ version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.5" @@ -374,6 +868,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "8.0.0" @@ -423,12 +927,53 @@ version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -438,6 +983,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -447,6 +1024,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.10" @@ -456,6 +1063,50 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -468,6 +1119,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "same-file" version = "1.0.6" @@ -477,6 +1134,47 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -492,6 +1190,31 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -509,6 +1232,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.44.1" @@ -516,11 +1255,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", + "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", + "tracing", "windows-sys 0.52.0", ] @@ -535,6 +1277,159 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -547,6 +1442,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "walkdir" version = "2.5.0" @@ -557,6 +1458,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -641,9 +1551,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-sys" @@ -726,3 +1636,23 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index c837473..144497d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,5 @@ clap = { version = "4.5.32", features = ["derive"] } chrono = "0.4.40" notify = "8.0.0" tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread", "io-std", "fs", "signal", "sync"] } +console-subscriber = "0.4.1" +home = "0.5.11" diff --git a/docs/rust_coding_challenge_filemonitor.pdf b/docs/rust_coding_challenge_filemonitor.pdf new file mode 100644 index 0000000..7ab3278 Binary files /dev/null and b/docs/rust_coding_challenge_filemonitor.pdf differ diff --git a/src/dir_monitor.rs b/src/dir_monitor.rs index e325b3c..d98f9a9 100644 --- a/src/dir_monitor.rs +++ b/src/dir_monitor.rs @@ -1,8 +1,11 @@ use std::path::{Path, PathBuf}; -use notify::{EventKind, RecommendedWatcher, Watcher}; +use notify::{RecommendedWatcher, Watcher}; +use crate::event::Event; use crate::monitored_files::MonitoredFiles; +use crate::notify_sender::NotifySender; +use crate::util::strip_path_prefix; @@ -15,10 +18,12 @@ pub struct DirMonitor /// The directory to monitor. dir: PathBuf, + /// The files that are being monitored within the directory. monitored_files: MonitoredFiles, } + impl DirMonitor { /// Create a new directory monitor for the desired directory. @@ -40,7 +45,7 @@ impl DirMonitor // Start from the directory and add each file to our list, // then recurse through all sub directories and add them to // the list and repeat. - match scan_dir(&mut self.monitored_files, &self.dir) + match scan_dir(&self.dir, &mut self.monitored_files, &self.dir) { Ok(_) => { @@ -61,80 +66,65 @@ impl DirMonitor /// ``` pub fn print_monitored_files(&self) { - self.monitored_files.print(&self.dir); + println!("{}", self.monitored_files); } - /// + /// Start monitoring the directory asyncronously and process changes within the directory + /// until a termination message is received. pub async fn monitor(&mut self, mut term_receiver: tokio::sync::watch::Receiver) { let mut running: bool = true; - //let (notify_sender, notify_receiver) = std::sync::mpsc::channel::>(); - let (notify_sender, notify_receiver) = tokio::sync::mpsc::channel::>(100); - let mut fs_watcher: RecommendedWatcher = match notify::recommended_watcher(notify_sender) - { - Ok(watcher) => { watcher } - Err(e) => { eprintln!("Unable to create watcher: {}", e); panic!(); } - }; - fs_watcher.watch(&self.dir, notify::RecursiveMode::Recursive).unwrap(); + // Setup the notify crate to watch the INBOX directory. + let tokio_runtime = tokio::runtime::Handle::current(); + let (notify_sender, mut notify_receiver) = tokio::sync::mpsc::channel::(10); + let wrapped_sender = NotifySender::new(tokio_runtime, notify_sender); + let mut fs_watcher: RecommendedWatcher = + match notify::recommended_watcher(wrapped_sender) + { + Ok(watcher) => { watcher } + Err(e) => + { + // Just panic because we cannot watch the directories so the program + // would be useless and this saves from having to press CTRL-C. + eprintln!("Unable to create watcher: {}", e); + panic!(); + } + }; + if let Err(e) = fs_watcher.watch(&self.dir, notify::RecursiveMode::Recursive) + { + // Just panic because we cannot watch the directories so the program + // would be useless and this saves from having to press CTRL-C. + eprintln!("Error trying to watch the directory: {}", e); + panic!(); + } + + + // Loop until termination processing events from the notify watcher. while running { - // Listen for file changes until termination signal is received. - match notify_receiver.recv().await + // We are listening for messages from either the notify receiver or + // the termination receiver. When ever one of them comes across we + // will process it. + tokio::select! { - Ok(msg) => + // Handle listening for the notify watcher events. + // These are the changes that we care about from the file system. + Some(mut event) = notify_receiver.recv() => { - match msg - { - Ok(event) => - { - match event.kind - { - EventKind::Create(_) => - { - println!("File Created: {:?}", event.paths); - } - EventKind::Modify(_) => - { - println!("File Modified: {:?}", event.paths); - } - EventKind::Remove(_) => - { - println!("File Removed: {:?}", event.paths); - } - _ => - { - } - } - } - - Err(e) => - { - eprintln!("{}", e); - } - } + event.make_paths_relative(&self.dir); + println!("{}", event); + self.monitored_files.process_event(event); } - Err(e) => - { - eprintln!("Error receiving notify event: {}", e); - } - } - // Handle listening for the termination message, the boolean value will - // be changed to false when we are meant to terminate. - match term_receiver.has_changed() - { - Ok(_) => + // Handle listening for the termination message, the boolean value will + // be changed to false when we are meant to terminate. + _ = term_receiver.changed() => { running = *term_receiver.borrow_and_update(); } - - Err(e) => - { - eprintln!("Unable to receive: {}", e); - } } } } @@ -144,7 +134,7 @@ impl DirMonitor /// Scans a directory, and all of its sub directories, and creates a list of the files /// inside and their last modification time. -fn scan_dir(monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result<()> +fn scan_dir(base_dir: &Path, monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result<()> { let mut dir_list: Vec = Vec::new(); @@ -169,14 +159,14 @@ fn scan_dir(monitored_files: &mut MonitoredFiles, dir: &Path) -> std::io::Result { let last_mod_time: std::time::SystemTime = meta.modified()?; - monitored_files.add(&file.path(), last_mod_time); + monitored_files.add(&strip_path_prefix(&file.path(), base_dir), last_mod_time); } } // Recursively scan the sub directories. for sub_dir in dir_list { - scan_dir(monitored_files, &sub_dir)?; + scan_dir(base_dir, monitored_files, &sub_dir)?; } Ok(()) diff --git a/src/event.rs b/src/event.rs index 43cbf2f..b753795 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,35 +1,83 @@ use std::time::SystemTime; +use crate::util::strip_path_prefix; + +/// The events we are worried about. /// +/// These will be sent as messages in a channel. pub enum Event { - /// + /// A file has been created. New { - /// - path: String, + /// The path to the file. + path: std::path::PathBuf, - /// + /// The modification time of the file. mod_time: SystemTime }, - /// + /// A file has been modified. Modify { - /// - path: String, + /// The path to the file. + path: std::path::PathBuf, - /// + /// The modification time of the file. mod_time: SystemTime }, - /// + /// A file has been renamed, and it stayed in a monitored directory. + Rename + { + /// The path of the file that was renamed. + from: std::path::PathBuf, + + /// The path that the file was renamed to. + to: std::path::PathBuf, + + /// The modification time of the file. + mod_time: SystemTime + }, + + /// A file has been removed. Delete { - /// - path: String + /// The path of the removed file. + path: std::path::PathBuf + } +} + + + +impl Event +{ + /// Take all the paths in the Event and makes them relative + /// to the base_dir directory. + pub fn make_paths_relative(&mut self, base_dir: &std::path::Path) + { + match self + { + Event::New { path, mod_time: _ } => + { + *path = strip_path_prefix(&path, base_dir); + } + Event::Modify { path, mod_time: _ } => + { + *path = strip_path_prefix(&path, base_dir); + } + Event::Rename { from, to, mod_time: _ } => + { + *from = strip_path_prefix(&from, base_dir); + *to = strip_path_prefix(&to, base_dir); + } + Event::Delete { path } => + { + *path = strip_path_prefix(&path, base_dir); + } + } } } @@ -40,17 +88,22 @@ impl std::fmt::Display for Event { match self { - Event::New(_) => + Event::New { path, mod_time: _ } => { - write!(f, "[NEW] {}", self.path) + write!(f, "[NEW] {}", path.display()) } - Event::Modify(_) => + Event::Modify { path, mod_time: _ } => { - write!(f, "[MOD] {}", self.path) + write!(f, "[MOD] {}", path.display()) } - Event::Delete => + Event::Rename { from, to, mod_time: _ } => { - write!(f, "[DEL] {}", self.path) + writeln!(f, "[DEL] {}", from.display())?; + write!(f, "[NEW] {}", to.display()) + } + Event::Delete { path } => + { + write!(f, "[DEL] {}", path.display()) } } } diff --git a/src/main.rs b/src/main.rs index c604d1e..f47333b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ mod dir_monitor; mod event; mod monitored_files; +mod notify_sender; +mod util; @@ -33,19 +35,28 @@ struct Options /// Creates the given directory, and its parents, if it does not exist. -fn create_directory(dir: &str) -> bool +fn create_directory(dir: &str) -> Option { - match std::fs::create_dir_all(dir) + let mut inbox_dir = String::from(dir); + if inbox_dir.starts_with("~") + { + if let Some(path) = home::home_dir() + { + inbox_dir = inbox_dir.replace("~", &path.display().to_string()); + } + } + + match std::fs::create_dir_all(inbox_dir.clone()) { Ok(_) => { - true + Some(inbox_dir) } Err(e) => { eprintln!("Creating directory error: {}", e); - false + None } } } @@ -72,14 +83,13 @@ async fn main() { let options: Options = Options::parse(); - println!("Inbox: `{}`", options.inbox_dir); - if create_directory(&options.inbox_dir) + if let Some(inbox_dir) = create_directory(&options.inbox_dir) { // This is our keep running channel. False will be sent // when the application is meant to be closed. let (sender, receiver) = watch::channel(true); - let mut directory_monitor: DirMonitor = DirMonitor::new(&options.inbox_dir); + let mut directory_monitor: DirMonitor = DirMonitor::new(&inbox_dir); // Program workflow step 1. // Async is not needed here. This can be done syncronously because @@ -99,11 +109,20 @@ async fn main() // preferably a green thread, but a fiber is sufficient // and less resource intensive. directory_monitor.monitor(receiver).await; + + // Program workflow step 4. + // Async is not needed here. This can be done + // syncronously because it is the final/clean up + // step of the program. + // + // This is done here because we have moved the directory + // monitor inside this task. + directory_monitor.print_monitored_files(); }); // Run until Ctrl-C is pressed. // Once it is pressed it will send a message to stop. - let term_task = tokio::spawn(async move + let _term_task = tokio::spawn(async move { // Program workflow step 3. // Async with message passing so it can run and listen @@ -115,11 +134,10 @@ async fn main() // Only need to wait for the monitor task to finish because it will only // finish once the termination task has finished. - mon_task.await; - - // Program workflow step 4. - // Async is not needed here. This can be done syncronously because it - // is the final/clean up step of the program. - directory_monitor.print_monitored_files(); + match mon_task.await + { + Ok(_) => {} + Err(e) => { eprintln!("Error during monitoring task: {}", e); } + } } } diff --git a/src/monitored_files.rs b/src/monitored_files.rs index f1d92a8..5883f58 100644 --- a/src/monitored_files.rs +++ b/src/monitored_files.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::{PathBuf, Path}; use std::time::SystemTime; @@ -7,104 +8,96 @@ use crate::event::Event; -/// +/// The files currently being monitored and their last modification time. pub struct MonitoredFiles { - /// - mod_dates: Vec, - - /// - paths: Vec + /// A mapping of a Path to the last known modification time. + files: HashMap } impl MonitoredFiles { + /// Create a new empty set of monitored files. pub fn new() -> Self { MonitoredFiles { - mod_dates: Vec::new(), - paths: Vec::new() + files: HashMap::new() } } - pub fn process_event(&mut self, path: &Path, event: Event) + /// Process an event and change the monitored files. + pub fn process_event(&mut self, event: Event) { match event { - Event::New(creation_time) => + Event::New { path, mod_time } => { - self.add(path, creation_time); + self.add(&path, mod_time); } - Event::Modify(mod_time) => + Event::Modify { path, mod_time} => { self.modify(&path, mod_time); } - Event::Delete => + Event::Rename { from, to, mod_time } => + { + self.remove(&from); + self.add(&to, mod_time); + } + + Event::Delete { path } => { self.remove(&path); } } } - pub fn add(&mut self, path: &Path, time: SystemTime) + /// Add a file that is to be monitored.. + pub fn add(&mut self, path: &Path, mod_time: SystemTime) { - self.paths.push(PathBuf::from(path)); - self.mod_dates.push(time); + self.files.insert(PathBuf::from(path), mod_time); } + /// Modify the stored modifcation time of a monitored file. pub fn modify(&mut self, path: &Path, time: SystemTime) { - if let Some(index) = self.get_path_index(path) + if let Some(entry) = self.files.get_mut(path) { - self.mod_dates[index] = time; + *entry = time; } } + /// Remove a file from being monitored. pub fn remove(&mut self, path: &Path) { - if let Some(index) = self.get_path_index(path) - { - self.mod_dates.remove(index); - self.paths.remove(index); - } - } - - pub fn print(&self, base: &Path) - { - for file in 0..self.paths.len() - { - let date_time: DateTime = DateTime::from(self.mod_dates[file]); - - match &self.paths[file].strip_prefix(base) - { - Ok(rel_path) => - { - println!("[{}] {}", date_time.format("%d/%m/%Y %H:%M"), rel_path.display()); - } - - Err(e) => - { - eprintln!("Unable to strip base directory: {}", e); - } - } - } - } - - fn get_path_index(&self, path: &Path) -> Option - { - for (index, stored_path) in self.paths.iter().enumerate() - { - if path == stored_path - { - return Some(index); - } - } - - None + self.files.remove(path); + } +} + + +impl std::fmt::Display for MonitoredFiles +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result + { + let mut it = self.files.iter().peekable(); + while let Some((path, mod_time)) = it.next() + { + let date_time: DateTime = DateTime::from(*mod_time); + + if !it.peek().is_none() + { + writeln!(f, "[{}] {}", date_time.format("%m/%d/%Y %H:%M"), path.display())?; + } + else + { + write!(f, "[{}] {}", date_time.format("%m/%d/%Y %H:%M"), path.display())?; + } + } + + Ok(()) } } diff --git a/src/notify_sender.rs b/src/notify_sender.rs new file mode 100644 index 0000000..4dcad0c --- /dev/null +++ b/src/notify_sender.rs @@ -0,0 +1,245 @@ +use std::path::PathBuf; + +use crate::util::get_file_mod_time; + + + +/// NotifySender is a newtype idiom around the tokio mpsc Sender. +/// +/// This was done to allow the tokio mpsc Sender to be a Notify crate +/// EventHandler. +pub struct NotifySender +{ + /// The tokio runtime to spawn event handling tasks on. + tokio_runtime: tokio::runtime::Handle, + + /// The channel to send event messages on. + sender: tokio::sync::mpsc::Sender +} + + + +impl NotifySender +{ + /// Create a new type that wraps the tokio mpsc Sender. + pub fn new(tokio_runtime: tokio::runtime::Handle, + sender: tokio::sync::mpsc::Sender) + -> Self + { + NotifySender + { + tokio_runtime, + sender + } + } +} + +impl notify::EventHandler for NotifySender +{ + fn handle_event(&mut self, event: notify::Result) + { + match event + { + Ok(notify_event) => + { + // Process the notify event and turn it into an event + // our crate is interested in, one of our Events. If + // we get a valid one then create a tokio task that will + // send the event out as a message. + if let Some(monitor_event) = process_notify_event(notify_event) + { + let notify_sender = self.sender.clone(); + + self.tokio_runtime.spawn(async move + { + if let Err(e) = notify_sender.send(monitor_event).await + { + eprintln!("Notify EventHandler: {}", e); + } + }); + } + } + + Err(e) => + { + eprintln!("Notify crate error: {}", e); + } + } + } +} + +/// Processes the notify crate events into events that our program cares about. +fn process_notify_event(event: notify::Event) -> Option +{ + match event.kind + { + notify::EventKind::Create(kind) => + { + process_create_event(kind, event.paths) + } + + notify::EventKind::Modify(kind) => + { + process_modify_event(kind, event.paths) + } + + notify::EventKind::Remove(kind) => + { + process_remove_event(kind, event.paths) + } + + _ => { None } + } +} + +/// Process the Create event into a New event. +fn process_create_event(event: notify::event::CreateKind, + paths: Vec) + -> Option +{ + if event == notify::event::CreateKind::File + { + let mod_time: std::time::SystemTime = + match get_file_mod_time(&paths[0]) + { + Ok(time) => { time } + + Err(e) => + { + eprintln!("Unable to open file to retrieve modification time: {}", + e); + std::time::SystemTime::now() + } + }; + + return Some(crate::event::Event::New + { + path: paths[0].clone(), + mod_time: mod_time + }); + } + + None +} + +/// Process the modify event into either a Modify event or a Rename event. +fn process_modify_event(event: notify::event::ModifyKind, + paths: Vec) + -> Option +{ + match event + { + // This is just handling the events. On my system they came across as Any for + // the MetadataKind, but the documentation seemed to hint that modification + // times should be on write events. So for the sake of being done without testing + // this on different platforms the Any modification event will trigger a time lookup. + notify::event::ModifyKind::Any | notify::event::ModifyKind::Metadata(_) => + { + let mod_time: std::time::SystemTime = + match get_file_mod_time(&paths[0]) + { + Ok(time) => { time } + + Err(e) => + { + eprintln!("Unable to open file to retrieve modification time: {}", + e); + std::time::SystemTime::now() + } + }; + + Some(crate::event::Event::Modify + { + path: paths[0].clone(), + mod_time: mod_time + }) + } + + // Handling a rename event so that during testing when a file is + // moved it is still tracked. + notify::event::ModifyKind::Name(mode) => + { + match mode + { + // This is for when a file is renamed and both the to and from + // are in the monitored directories. + notify::event::RenameMode::Both => + { + let mod_time: std::time::SystemTime = + match get_file_mod_time(&paths[1]) + { + Ok(time) => { time } + + Err(e) => + { + eprintln!("Unable to open file to retrieve modification time: {}", + e); + std::time::SystemTime::now() + } + }; + + Some(crate::event::Event::Rename + { + from: paths[0].clone(), + to: paths[1].clone(), + mod_time: mod_time + }) + } + + // This is for when a file is renamed and only the from + // file is in the monitored directories. + notify::event::RenameMode::From => + { + Some(crate::event::Event::Delete + { + path: paths[0].clone(), + }) + } + + // This is for when a file is renamed and both the to file + // is in the monitored directories. + notify::event::RenameMode::To => + { + let mod_time: std::time::SystemTime = + match get_file_mod_time(&paths[0]) + { + Ok(time) => { time } + + Err(e) => + { + eprintln!("Unable to open file to retrieve modification time: {}", + e); + std::time::SystemTime::now() + } + }; + + Some(crate::event::Event::New + { + path: paths[0].clone(), + mod_time: mod_time + }) + } + + _ => { None } + } + } + + _ => { None } + } +} + +/// Process the remove event into a Delete event. +fn process_remove_event(event: notify::event::RemoveKind, + paths: Vec) + -> Option +{ + if event == notify::event::RemoveKind::File + { + return Some(crate::event::Event::Delete + { + path: paths[0].clone(), + }); + } + + None +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..8342c35 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,36 @@ +/// Return the modification time of a file. +/// +/// If it is unable to get the modification time from the file system +/// it will default to the current system time. +pub fn get_file_mod_time(path: &std::path::Path) -> std::io::Result +{ + let file = std::fs::File::open(path)?; + let meta = file.metadata()?; + + if meta.is_file() + { + return Ok(meta.modified()?); + } + + Ok(std::time::SystemTime::now()) +} + +/// Create a new PathBuf that is the same as path but has the base removed if it can. +pub fn strip_path_prefix(path: &std::path::Path, base: &std::path::Path) -> std::path::PathBuf +{ + match &path.strip_prefix(base) + { + Ok(rel_path) => + { + let mut final_path = std::path::PathBuf::new(); + final_path.push("./"); + final_path.push(rel_path); + final_path + } + + Err(_) => + { + std::path::PathBuf::from(path) + } + } +}