initial commit

master
Spencer Kohan 4 years ago
commit 84402e475f

3
.gitignore vendored

@ -0,0 +1,3 @@
/target
/.dirsync
/scratch

661
Cargo.lock generated

@ -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",
]

@ -0,0 +1,16 @@
[package]
name = "dirsync"
version = "0.1.0"
authors = ["Spencer Kohan <spencerkohan@gmail.com>"]
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"

@ -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.

@ -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<String>,
// Initialize the .dirsync directory
#[structopt(subcommand)]
pub subcommand: Option<SubCommand>
}
#[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,
}

@ -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);
}
}

@ -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(())
}

@ -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<DebouncedEvent> {
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);
}
};
}

@ -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);
}
}
Loading…
Cancel
Save