From 84402e475f619798e7608131c620bfe084e99c47 Mon Sep 17 00:00:00 2001 From: Spencer Kohan Date: Mon, 13 Apr 2020 12:24:09 +0200 Subject: [PATCH] initial commit --- .gitignore | 3 + Cargo.lock | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 ++ README.md | 106 ++++++++ src/cli.rs | 41 ++++ src/config.rs | 106 ++++++++ src/init.rs | 33 +++ src/main.rs | 135 +++++++++++ src/remote.rs | 130 ++++++++++ 9 files changed, 1231 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/cli.rs create mode 100644 src/config.rs create mode 100644 src/init.rs create mode 100644 src/main.rs create mode 100644 src/remote.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c390ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/.dirsync +/scratch \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4f939c8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,661 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "dirsync" +version = "0.1.0" +dependencies = [ + "notify", + "serde", + "serde_json", + "ssh2", + "ssh_config", + "structopt", +] + +[[package]] +name = "filetime" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.8", +] + +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +dependencies = [ + "libc", +] + +[[package]] +name = "inotify" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" +dependencies = [ + "libc", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" + +[[package]] +name = "libc" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" + +[[package]] +name = "libssh2-sys" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb70f29dc7c31d32c97577f13f41221af981b31248083e347b7f2c39225a6bc" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "notify" +version = "4.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio", + "mio-extras", + "walkdir", + "winapi 0.3.8", +] + +[[package]] +name = "openssl-sys" +version = "0.9.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7717097d810a0f2e2323f9e5d11e71608355e24828410b55b9d4f18aa5f9a5d8" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.8", +] + +[[package]] +name = "pkg-config" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" + +[[package]] +name = "proc-macro-error" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "ryu" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" + +[[package]] +name = "ssh2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1a0bdfb47e1fa1eb80f67c7909cfddab49d4262025aa9e4f6e9c39ad77b482" +dependencies = [ + "bitflags", + "libc", + "libssh2-sys", + "parking_lot", +] + +[[package]] +name = "ssh_config" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08044686521f352c6d1f522ba349c3dd52813819fa8353b60bd3d2107f6dca0a" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6da2e8d107dfd7b74df5ef4d205c6aebee0706c647f6bc6a2d5789905c00fb" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a489c87c08fbaf12e386665109dd13470dcc9c4583ea3e10dd2b4523e5ebd9ac" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi 0.3.8", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +dependencies = [ + "winapi 0.3.8", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6a3d82c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dirsync" +version = "0.1.0" +authors = ["Spencer Kohan "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +notify = "4.0.12" +# clap = { git = "https://github.com/clap-rs/clap/" } +structopt = { version = "0.3" } +serde = { version = "1.0.106", features = ["derive"] } +serde_json = "1.0" +ssh2 = "0.8" +ssh_config = "0.1.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..521c751 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# Dirsync + +Dirsync is a tool for live-updating the contents of a remote directoy to match the contents of a local directory. Dirsync is similar to rsync, except instead of working as a one-time operation, dirsync watches the local directory and pushes changes to the remote host whenever a file is changed locally. In fact, dirsync is built upon rsync. + +This tool should be easy to understand for those who are already familiar with `rsync` and `ssh`. + +## Installation + +The easiest way to install + +**Note: this crate relies on openssl to be installed on the system. Instructions can be found [here](https://docs.rs/openssl/0.10.29/openssl/) + +## Usage + +### Initialization + +Before dirsync can be used for a local directory, it has to be initialized. This sets up the `.dirsync` directory which is used to manage the configuration. This is handled by the `init` command. + +For instance, if you would use the following command to synch a directory using `rsync`: + +``` +$ rsync -r . myUser@myRemoteHost:/path/to/sync +``` + +then you would use the following initialization configuration: + +``` +$ dirsync init -u myUser -h myRemoteHost -p /path/to/sync` +``` + +### Synching + +Once initialization has taken place, a dirsync session can be started with the simple command: + +``` +$ dirsync +``` + +While the session is running, any changes to the local directory will be pushed to the remote specified in the configuration. + +### Configuration + +All configuration of `dirsync` is handled by the `.dirsync` directory, which is created by `$ dirsync init`. This directory has the following contents: + +``` +.dirsync/ +├── actions +│   └── onFileDidChange +│   └── remote +├── config.json +└── ignore +``` + +The elements here are: + +#### config.json + +This is a file which contains the configuration options for specifying the remote host, and the remote directory. It has this format: + +``` +{ + "remote": { + "root": "path/to/remote", + "host": "hostName", + "user": "userName", + "port": "22", // optional + "identityFile": "id_rsa" // optional + }, + "ignoreGitignore" : true +} +``` + +The felds are: + +- `remote.root`: the path to the directory which will be synced on the remote host. + +- `remote.host`: the hostname of the remote host. + +- `remote.port`: the ssh port on the remote host. If the port is omitted, the default value is 22. + +- `remote.identiyFile` is the identity file which should be used to connect to the host over ssh. Dirsync currently only supports authentication via ssh keys. If this value is omitted, the ssh-agent's default key will be used. + +- `ignoreGitignore`: an option to specify whether paths listed in the top-level .gitignore file shoul be ignored by dirsync. Default is true. + +#### ignore file + +The ignore file specifies paths which should not be synced by dirsync. The format of the ignore file is identical to what would be passed to the `--exclude-from` option of rsync. + +### Action triggers + +The `.dirsync/actions` directory houses executables which are triggered by certain dirsync events. + +Currently the only action supported is `onSyncDidFinish`. This action is triggered after any sync event from the local to the remote (i.e. after a local file in the watched directory has changed, and the resulting sync has completed). + +What this means is, any script located at `.dirsync/actionos/onSyncDidFinish` will be executed on the remote following any sync event. + +So for example, if you were synchoronizing a rust project with your remote host, and you wanted to build the project every time a change is pushed, you could implement this `onSyncDidFinish` event: + +``` +#!/bin/bash +cargo build +``` + +This script will always be executed from the root of the synced directory. + + diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..0ecde76 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,41 @@ +use structopt::StructOpt; +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +#[derive(StructOpt)] +#[derive(Clone)] +pub enum SubCommand { + #[structopt(name="init")] + Init(RemoteConfigRecord) +} + +#[derive(Debug)] +#[derive(StructOpt)] +#[derive(Clone)] +#[structopt(version = "0.1", author = "Spencer Kohan")] +pub struct CliOptions { + // The locaal root directory to be synchronized + pub source: Option, + // Initialize the .dirsync directory + #[structopt(subcommand)] + pub subcommand: Option +} + + + +#[derive(Debug)] +#[derive(StructOpt)] +#[derive(Clone)] +#[derive(Deserialize, Serialize)] +pub struct RemoteConfigRecord { + /// The remote root of the sync directory + #[structopt(short = "r", long = "root")] + pub root: String, + /// The remote host + #[structopt(short = "h", long = "host")] + pub host: String, + + /// The remote user + #[structopt(short = "u", long = "user")] + pub user: String, +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..30e1107 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,106 @@ + +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; +use crate::cli::RemoteConfigRecord; +// use std::error::Error; +// use ssh_config::SSHConfig; + +use crate::cli::CliOptions; + +fn default_as_true() -> bool { + true +} + +#[derive(Debug)] +#[derive(Deserialize, Serialize)] +pub struct Config { + #[serde(alias = "ignoreGitignore", default = "default_as_true")] + pub ignore_gitignore: bool, + pub remote: RemoteConfigRecord +} + +impl Config { + pub fn new(remote: RemoteConfigRecord) -> Config { + return Config { + ignore_gitignore: true, + remote: remote + }; + } +} + + +impl RemoteConfigRecord { + fn host_string(&self) -> String { + let mut s: String = String::new(); + let host = &format!( + "{}@{}", + &self.user.clone(), + &self.host.clone()); + s.push_str(host); + return s; + } +} + +#[derive(Debug)] +pub struct SessionConfig { + // The root directory to sync to the remote + pub local_root: String, + pub remote: RemoteConfigRecord, + pub ignore_gitignore: bool, + +} + + +impl SessionConfig { + + pub fn host_port_string(&self) -> String { + let mut s: String = String::new(); + let host = &format!( + "{}:22", + &self.remote.host.clone()); + s.push_str(host); + return s; + } + + pub fn exclude_path(&self) -> PathBuf { + let mut path = PathBuf::new(); + path.push(self.local_root.clone()); + path.push(".dirsync"); + path.push("ignore"); + return path; + } + + pub fn destination(&self) -> String { + let mut s: String = String::new(); + s.push_str(&self.remote.host_string().as_str()); + s.push_str(":"); + s.push_str(self.remote.root.clone().as_str()); + return s; + + } + + pub fn with_local_root(local_root: &String) -> SessionConfig { + + let mut config_path = PathBuf::new(); + config_path.push(local_root.clone()); + config_path.push(".dirsync"); + config_path.push("config.json"); + + let config_string = fs::read_to_string(config_path) + .expect("failed to read config"); + let config: Config = serde_json::from_str(&config_string) + .expect("failed to deserialize json"); + + return SessionConfig { + local_root: local_root.clone(), + remote: config.remote, + ignore_gitignore: config.ignore_gitignore + } + } + + pub fn get(args: CliOptions) -> SessionConfig { + let local_root = args.source.unwrap_or(".".to_string()); + return SessionConfig::with_local_root(&local_root); + } +} \ No newline at end of file diff --git a/src/init.rs b/src/init.rs new file mode 100644 index 0000000..292d01e --- /dev/null +++ b/src/init.rs @@ -0,0 +1,33 @@ +use std::fs; +use crate::cli::RemoteConfigRecord; +use crate::config::Config; +use std::fs::File; +use std::io::prelude::*; + +#[derive(Debug)] +pub enum InitError { + Io(std::io::Error), + Serde(serde_json::error::Error) +} + +fn create_dirsync_dirs() -> Result<(), std::io::Error> { + fs::create_dir_all("./.dirsync/actions/onSyncDidFinish")?; + Ok(()) +} + +pub fn init_dirsync_dir(remote_options: RemoteConfigRecord) -> Result<(), InitError> { + + create_dirsync_dirs().map_err(|err| InitError::Io(err))?; + + let _ignore_file = File::create("./.dirsync/ignore").map_err(|err| InitError::Io(err))?; + let mut config_file = File::create("./.dirsync/config.json").map_err(|err| InitError::Io(err))?; + + let config = Config::new(remote_options); + + let json = serde_json::to_string_pretty(&config).map_err(|err| InitError::Serde(err))?; + + config_file.write_all(json.as_bytes()).map_err(|err| InitError::Io(err))?; + + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..005441b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,135 @@ +mod config; +mod remote; +mod cli; +mod init; + +extern crate notify; + +use notify::{Watcher, RecursiveMode, watcher, DebouncedEvent}; +use std::sync::mpsc::channel; +use std::time::Duration; +use crate::config::SessionConfig; +use crate::cli::SubCommand; +use crate::cli::CliOptions; +use structopt::StructOpt; +use std::io::prelude::*; + + +// Perform rsync from source to destination +fn rsync(config: &config::SessionConfig) { + use std::process::Command; + + // we sync actions explicitly here, since they might be ignored otherwise + let dirsync_dir_local = &format!("{}/.dirsync/actions", &config.local_root); + let dirsync_dir_remote = &format!("{}", &config.destination()); + + let output = &Command::new("rsync") + .arg("-v") // verbose output + .arg("-r") + .arg(dirsync_dir_local) + .arg(dirsync_dir_remote) + .output() + .expect("failed to execute process"); + + println!("executing rsync: {} {}", &dirsync_dir_local, &dirsync_dir_remote); + println!("status: {}", output.status); + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + assert!(output.status.success()); + + if config.ignore_gitignore { + + let output = &Command::new("rsync") + .arg("-v") // verbose output + .arg("-r") + .arg(format!("--exclude-from={}", config.exclude_path().to_str().unwrap())) + .arg("--exclude-from=.gitignore") + .arg(&config.local_root) + .arg(&config.destination()) + .output() + .expect("failed to execute process"); + + println!("executing rsync: {} {}", &config.local_root, &config.destination()); + println!("status: {}", output.status); + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + assert!(output.status.success()); + + } else { + + let output = &Command::new("rsync") + .arg("-v") // verbose output + .arg("-r") + .arg(format!("--exclude-from={}", config.exclude_path().to_str().unwrap())) + .arg(&config.local_root) + .arg(&config.destination()) + .output() + .expect("failed to execute process"); + + println!("executing rsync: {} {}", &config.local_root, &config.destination()); + println!("status: {}", output.status); + std::io::stdout().write_all(&output.stdout).unwrap(); + std::io::stderr().write_all(&output.stderr).unwrap(); + assert!(output.status.success()); + + } + +} + +fn filter(event: DebouncedEvent) -> Option { + match event { + DebouncedEvent::NoticeWrite(_) => None, + DebouncedEvent::NoticeRemove(_) => None, + DebouncedEvent::Chmod(_) => None, + DebouncedEvent::Rescan => None, + DebouncedEvent::Error(_, _) => None, + _ => Some(event) + } +} + + +fn start_main_loop(config: &SessionConfig) { + + println!("config: {:?}", config); + + rsync(&config); + let mut remote = remote::Remote::connect(config); + remote.execute_if_exists("onSyncDidFinish"); + + // Create a channel to receive the events. + let (tx, rx) = channel(); + let mut watcher = watcher(tx, Duration::from_millis(20)).unwrap(); + watcher.watch(config.local_root.clone(), RecursiveMode::Recursive).unwrap(); + + loop { + match rx.recv() { + Ok(event) => { + println!("handling event: {:?}", event); + match filter(event) { + Some(_) => { + rsync(&config); + println!("Executing onSyncDidFinish action"); + remote.execute_if_exists("onSyncDidFinish"); + }, + None => println!("ignoring event") + }; + + }, + Err(e) => println!("watch error: {:?}", e), + } + } +} + + +fn main() { + + let opts = CliOptions::from_args(); + + match opts.subcommand { + Some(SubCommand::Init(remote_config)) => init::init_dirsync_dir(remote_config).unwrap(), + _ => { + let config = SessionConfig::get(opts); + start_main_loop(&config); + } + }; +} diff --git a/src/remote.rs b/src/remote.rs new file mode 100644 index 0000000..f98f96a --- /dev/null +++ b/src/remote.rs @@ -0,0 +1,130 @@ +extern crate ssh2; +use std::io::prelude::*; +use std::net::TcpStream; +use std::path::PathBuf; + + +use ssh2::Session; +use ssh2::ExtendedData; +use crate::config::SessionConfig; + +// pub fn echo() { + +// let tcp = TcpStream::connect("pc:22").unwrap(); +// let mut sess = Session::new().unwrap(); +// sess.set_tcp_stream(tcp); +// sess.handshake().unwrap(); + +// // Try to authenticate with the first identity in the agent. +// sess.userauth_agent("skohan").unwrap(); + +// // Make sure we succeeded +// assert!(sess.authenticated()); + +// let mut channel = sess.channel_session().unwrap(); +// channel.exec("ls").unwrap(); +// let mut s = String::new(); +// channel.read_to_string(&mut s).unwrap(); +// println!("{}", s); +// channel.wait_close(); +// println!("{}", channel.exit_status().unwrap()); + +// } + +pub struct Remote { + session: ssh2::Session, + root: PathBuf +} + +impl Remote { + + // todo: this shouold take configuration arguemnts + pub fn connect(config: &SessionConfig) -> Remote { + + let tcp = TcpStream::connect( + &config.host_port_string().as_str() + ).unwrap(); + let mut sess = Session::new().unwrap(); + sess.set_tcp_stream(tcp); + sess.handshake().unwrap(); + + // Try to authenticate with the first identity in the agent. + sess.userauth_agent(&config.remote.user.clone().as_str()).unwrap(); + + // Make sure we succeeded + assert!(sess.authenticated()); + + let mut root = PathBuf::new(); + root.push( &config.remote.root.clone() ); + + return Remote { + session: sess, + root: root + } + + } + + fn exec(&mut self, command: &str) -> String { + + let path = self.root.clone(); + let path_str = path.to_str().unwrap(); + let cmd = &format!("cd {} && {}", &path_str, &command); + + let channel = &mut self.session.channel_session().unwrap(); + channel.exec( + &cmd + ).unwrap(); + + let mut s = String::new(); + channel.read_to_string(&mut s).unwrap(); + println!("exec: {}", &cmd); + let _ = channel.wait_close(); + return s; + } + + fn exec_stream(&mut self, command: &str) { + let path = self.root.clone(); + let path_str = path.to_str().unwrap(); + let cmd = &format!("cd {} && {}", &path_str, &command); + + let channel = &mut self.session.channel_session().unwrap(); + + let mut channel_out = channel.stream(0); + + channel.handle_extended_data(ExtendedData::Merge).unwrap(); + channel.request_pty("term", None, None).unwrap(); + channel.exec( + &cmd + ).unwrap(); + + std::io::copy(&mut channel_out, &mut std::io::stdout()).unwrap(); + let _ = channel.wait_close(); + } + + fn file_exists(&mut self, filename: &str) -> bool { + let command = &format!("test -f {} && echo 1 || echo 0", filename); + let s = self.exec(&command); + return s.as_str() == "1\n"; + } + + pub fn execute_if_exists(&mut self, event: &str) { + let mut path = self.root.clone(); + path.push(".dirsync/actions"); + path.push(event); + path.push("remote"); + let path_str = path.to_str().unwrap(); + + if !self.file_exists(&path_str) { + println!("file does not exist: {}", &path_str); + return + } + + let command1 = &format!("chmod +x {}", &path_str); + let _ = self.exec(&command1); + + let command2 = &format!("{}", &path_str); + self.exec_stream(&command2); + + } + +} \ No newline at end of file