From 41ee5c3be8afc188f133d856775994e874a63032 Mon Sep 17 00:00:00 2001 From: Matthieu Jolimaitre Date: Tue, 17 Jun 2025 14:07:59 +0200 Subject: [PATCH] Init. --- .gitignore | 1 + Cargo.lock | 577 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 29 +++ README.md | 24 ++ build | 7 + log.log | 7 + rustfmt.toml | 3 + src/common.rs | 6 + src/play.rs | 47 ++++ src/rec.rs | 114 ++++++++++ test/.gitignore | 1 + test/run | 25 +++ 12 files changed, 841 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100755 build create mode 100644 log.log create mode 100644 rustfmt.toml create mode 100644 src/common.rs create mode 100644 src/play.rs create mode 100644 src/rec.rs create mode 100644 test/.gitignore create mode 100755 test/run diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2b7bbbc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,577 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + +[[package]] +name = "fsr" +version = "0.1.0" +dependencies = [ + "clap", + "ctrlc", + "inotify", + "ron", + "serde", + "tokio", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags", + "futures-core", + "inotify-sys", + "libc", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ron" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" +dependencies = [ + "base64", + "bitflags", + "serde", + "serde_derive", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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 = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..52170e1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "fsr" +version = "0.1.0" +edition = "2024" + +[lib] +name = "common" +path = "src/common.rs" + +[[bin]] +name = "fsr-rec" +path = "src/rec.rs" +required-features = ["notify"] + +[[bin]] +name = "fsr-play" +path = "src/play.rs" + +[features] +default = ["notify"] +notify = ["dep:inotify"] + +[dependencies] +clap = { version = "4.5.40", features = ["derive"] } +ctrlc = "3.4.7" +ron = "0.10.1" +serde = { version = "1.0.219", features = ["derive"] } +tokio = { version = "1.45.1", features = ["full"] } +inotify = { version = "0.11.0", optional = true } diff --git a/README.md b/README.md new file mode 100644 index 0000000..e633f3e --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Filesystem recorder + +Utility to record and replay file system events to produce test scenario on +file-watching applications. + +## Build + +### Requirements + +This project use case is recording on linux and playing on windows. As such, the +recorder is playing + +In order to make `fsr-play` available on windows, `fsr-rec` is split into a +default feature that may be disabled during windows compilation. + +To build all possible artefacts for both linux and windows : + +``` +./build +``` + +> **note** Building for windows on linux requires the cargo-xwin program. It may +> be installed through your package manager or through cargo +> `cargo install cargo-xwin` diff --git a/build b/build new file mode 100755 index 0000000..7d85103 --- /dev/null +++ b/build @@ -0,0 +1,7 @@ +#!/usr/bin/bash +set -e +cd "$(dirname "$(realpath "$0")")" + + +cargo build --release --target=x86_64-unknown-linux-musl +cargo xwin build --release --target=x86_64-pc-windows-msvc --bin=fsr-play --no-default-features diff --git a/log.log b/log.log new file mode 100644 index 0000000..4db3495 --- /dev/null +++ b/log.log @@ -0,0 +1,7 @@ +Create((0,0,0,0,0,0,0,0,0,0,0,0,229,150,15,28),"./tmp/f") +Append((0,0,0,0,0,0,0,0,0,0,0,1,68,178,33,106),"./tmp/f",[101]) +Append((0,0,0,0,0,0,0,0,0,0,0,1,68,183,67,159),"./tmp/f",[]) +Append((0,0,0,0,0,0,0,0,0,0,0,6,33,218,117,137),"./tmp/f",[110,118,105,101,32,100,101,32,98,117,115,32,99,97,110,110,101,114]) +Append((0,0,0,0,0,0,0,0,0,0,0,6,33,227,94,37),"./tmp/f",[]) +Append((0,0,0,0,0,0,0,0,0,0,0,10,165,15,163,126),"./tmp/f",[10,10,116,114,117,99,32,116,114,195,168,115,32,114,105,103,111,108,111]) +Append((0,0,0,0,0,0,0,0,0,0,0,10,165,20,17,175),"./tmp/f",[]) diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..ab10eb6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +hard_tabs = true +max_width = 120 +chain_width = 120 diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..069e768 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum Event { + Append([u8; 16], String, Vec), + Create([u8; 16], String), + Stop, +} diff --git a/src/play.rs b/src/play.rs new file mode 100644 index 0000000..cc6d70c --- /dev/null +++ b/src/play.rs @@ -0,0 +1,47 @@ +use std::{ + fs::{self, File}, + io::Write, + path::Path, + thread, + time::{Duration, Instant}, +}; + +use clap::Parser; +use common::Event; + +#[derive(Debug, Clone, clap::Parser)] +pub struct Args { + input_file: String, + scene_dir: String, +} + +pub fn main() { + let Args { scene_dir, input_file } = Args::parse(); + let content = fs::read_to_string(input_file).expect("Could not open input file."); + let events = content.lines().map(|e| ron::from_str::(e).expect("e")).collect::>(); + let count = events.len(); + println!("Playing {count} events in '{scene_dir}'."); + let start = Instant::now(); + let dir_path = Path::new(&scene_dir); + for event in events { + match event { + Event::Create(time, path) => { + let path = dir_path.join(path); + thread::sleep(remaining(start, time)); + File::create(path).expect("Failed to create file."); + } + Event::Append(time, path, content) => { + let path = dir_path.join(path); + let mut file = File::options().append(true).open(path).expect("Failed to open file for appending."); + thread::sleep(remaining(start, time)); + file.write_all(&content).expect("Failed to write into file for appending."); + } + _ => (), + } + } +} + +fn remaining(start: Instant, time: [u8; 16]) -> Duration { + let duration = Duration::from_nanos(u128::from_be_bytes(time) as u64); + duration.saturating_sub(start.elapsed()) +} diff --git a/src/rec.rs b/src/rec.rs new file mode 100644 index 0000000..d01f061 --- /dev/null +++ b/src/rec.rs @@ -0,0 +1,114 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process; + +use clap::Parser; +use common::Event; +use inotify::{EventMask, Inotify, WatchMask}; +use tokio::fs::File; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::sync::mpsc; +use tokio::time::Instant; +use tokio::{signal, task}; + +#[derive(Debug, Clone, clap::Parser)] +struct Args { + watched_dir: String, + output_file: String, +} + +#[tokio::main] +async fn main() { + let Args { + output_file, + watched_dir, + } = Args::parse(); + println!("Watching directory '{watched_dir}' recording to '{output_file}'."); + let recorder = Recorder::start(output_file).await; + tokio::spawn(DirWatcher::watch(watched_dir, recorder.sender.clone())); + tokio::spawn(async move { + println!("Press [ctrl + c] to stop recording."); + signal::ctrl_c().await.ok(); + recorder.sender.send(Event::Stop).await.expect("Event thread panicked.") + }); + recorder.handle.await.expect("Event thread panicked."); +} + +pub struct Recorder { + sender: mpsc::Sender, + handle: task::JoinHandle<()>, +} + +impl Recorder { + pub async fn start(output_path: impl AsRef) -> Self { + let mut options = File::options(); + options.create(true).write(true).truncate(true); + let output_file = options.open(output_path).await.expect("Could not open recorder output file."); + let (sender, receiver) = mpsc::channel(16_384); + let handle = tokio::spawn(Self::spin(output_file, receiver)); + Self { sender, handle } + } + + async fn spin(mut output_file: File, mut receiver: mpsc::Receiver) { + while let Some(event) = receiver.recv().await { + if let Event::Stop = event { + println!("Stoping."); + process::exit(0); + } + let serialized = ron::to_string(&event).unwrap() + "\n"; + output_file.write_all(serialized.as_bytes()).await.expect("Failed to write event into output file."); + } + } +} + +pub struct DirWatcher {} + +impl DirWatcher { + pub async fn watch(dir_path: impl AsRef, sender: mpsc::Sender) { + let start = Instant::now(); + let mut files = HashMap::new(); + let mut events = Self::dir_events(dir_path.as_ref().to_owned()).await; + while let Some((is_create, name)) = events.recv().await { + let path = dir_path.as_ref().join(name.clone()); + let path_str = path.to_str().unwrap().to_string(); + let date = start.elapsed().as_nanos().to_be_bytes(); + if is_create { + let file = File::open(&path).await.expect("Could not open newly created file."); + sender.send(Event::Create(date, name)).await.expect("Recorder thread panicked."); + files.insert(path_str, file); + } else { + if !files.contains_key(&path_str) { + let file = File::open(&path).await.expect("Could not open newly created file."); + files.insert(path_str.clone(), file); + } + let file = files.get_mut(&path_str).unwrap(); + let mut beuf = Vec::new(); + file.read_to_end(&mut beuf).await.expect("Failed to read moified file."); + sender.send(Event::Append(date, name, beuf)).await.expect("Recorder thread panicked."); + } + } + } + + async fn dir_events(dir_path: PathBuf) -> mpsc::Receiver<(bool, String)> { + let (sender, receiver) = mpsc::channel(16_384); + task::spawn_blocking(move || { + let mut watcher = Inotify::init().expect("Could not start notifier."); + watcher + .watches() + .add(dir_path, WatchMask::CREATE | WatchMask::MODIFY) + .expect("Could not add path to watcher."); + loop { + let mut buffer = [0; 1024]; + let events = watcher.read_events_blocking(&mut buffer).expect("Failed to watch events."); + for event in events { + if let Some(name) = event.name { + let is_create = event.mask.contains(EventMask::CREATE); + let name_str = name.to_str().expect("File name is not UTF."); + sender.blocking_send((is_create, name_str.into())).expect("Receiving thread panicked."); + } + } + } + }); + receiver + } +} diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..af7a17a --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +/wdir \ No newline at end of file diff --git a/test/run b/test/run new file mode 100755 index 0000000..10a8f3b --- /dev/null +++ b/test/run @@ -0,0 +1,25 @@ +#!/usr/bin/bash +set -e +cd "$(dirname "$(realpath "$0")")" + + +../build +rm -fr wdir +mkdir wdir wdir/rec wdir/play + + +../target/x86_64-unknown-linux-musl/release/fsr-rec wdir/rec wdir/scenario.ron & +rec_pid=$! + + +sleep 2s +echo "lorem" >> wdir/rec/main.txt +sleep 2s +echo "ipsum" >> wdir/rec/main.txt +sleep 2s +echo "dolor" >> wdir/rec/main.txt +sleep 2s +kill -INT $rec_pid + + +../target/x86_64-unknown-linux-musl/release/fsr-play wdir/scenario.ron wdir/play