Compare commits

...

21 commits

Author SHA1 Message Date
672094558d more precise event times 2023-06-29 18:42:10 +02:00
7430db6c5b bumb dependencies 2023-06-29 17:50:27 +02:00
f6e341a637 nix ci 2023-06-29 17:44:15 +02:00
e5a506f3fe reduce binary size 2021-02-20 19:37:55 +01:00
0e4a7eb35d cleanup dev-deps 2021-02-20 19:14:08 +01:00
d723a22d9b release action 2021-02-20 18:24:26 +01:00
347f2db450 cleanup makefile 2021-02-20 18:21:29 +01:00
90af4a497c better tests 2021-02-20 18:19:17 +01:00
49652b7288 update dependencies and better error reporting 2021-02-20 17:11:17 +01:00
95dcfedc38 update deploy key 2019-05-16 13:59:20 +02:00
80e8483eb4 adjust test to new output format 2019-05-16 13:31:34 +02:00
e8d0383762 update readme 2019-05-16 13:09:13 +02:00
43201550fc use json formatting for events 2019-05-16 13:06:02 +02:00
9cbfb0ff3e fmt 2019-05-16 12:32:59 +02:00
2a01af9db0 add travis badge 2019-03-28 19:42:15 +01:00
a19f406505 error formatting 2019-03-28 19:37:33 +01:00
5346518168 bump dependencies 2019-03-28 19:37:33 +01:00
cf784114c6 rust 2018 2019-03-28 19:37:33 +01:00
24cf14f50c use destructuring for args 2019-03-28 19:37:33 +01:00
6c25409d0d
Add build instructions 2019-03-28 18:20:44 +01:00
77be5f9dff extend readme 2018-04-17 13:43:13 +02:00
17 changed files with 1625 additions and 316 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

90
.github/workflows/nix.yaml vendored Normal file
View file

@ -0,0 +1,90 @@
name: "CI"
on:
pull_request:
push:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#check
clippy:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#clippy
test:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#test
matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- id: set-matrix
run: echo "matrix={\"target\":$(nix eval --json ".#targets.x86_64-linux")}" | tee $GITHUB_OUTPUT
build:
runs-on: ubuntu-latest
needs: [check, matrix]
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix.outputs.matrix)}}
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#${{ matrix.target }}
- uses: actions/upload-artifact@v3
with:
name: notify-redis-${{ matrix.target }}
path: result/bin/*
docker:
runs-on: ubuntu-latest
needs: [build, clippy, test]
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#dockerImage
- name: Push image
if: github.ref == 'refs/heads/master'
run: |
skopeo copy --dest-creds="${{ secrets.DOCKERHUB_USERNAME }}:${{ secrets.DOCKERHUB_TOKEN }}" "docker-archive:$(nix build .#dockerImage --print-out-paths)" "docker://icewind1991/notify-redis"

40
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: Release
on:
release:
types: [created]
jobs:
matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- id: set-matrix
run: echo "matrix={\"target\":$(nix eval --json ".#targets.x86_64-linux")}" | tee $GITHUB_OUTPUT
build:
runs-on: ubuntu-latest
needs: matrix
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix.outputs.matrix)}}
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v20
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: '${{ secrets.ATTIC_TOKEN }}'
- run: nix build .#${{ matrix.target }}
- name: Upload binary to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: result/bin/cube
asset_name: notify-redis-${{ matrix.target }}
tag: ${{ github.ref }}

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
/test
/target
**/*.rs.bk
.direnv
result

View file

@ -1,18 +0,0 @@
sudo: required
services:
- docker
- redis-server
install:
- docker pull ekidd/rust-musl-builder:latest
script:
- "./travis-build.sh notify-redis ${TRAVIS_OS_NAME}"
- "./test.sh ./notify-redis-${TRAVIS_OS_NAME}"
deploy:
provider: releases
api_key:
secure: h4tCundDFU+g7sVF6prABpg7vs/smKindNMYSfPFVXhJuhxZ27ibLtXzf+SiD5Crqy5g5MKOIP8tQxAKI/7TScPkL8rOljBE5zRo9sCCcy4rikDWgGPRkCCuLfv9UTcEN9L6nwMdfAGMlWtcEkd46QsaoCm1ht/FQXngjV/uLmZyoMoGY01uupl75u9zi5OQiYNkJPWge2ceoFGIKvc3Zvmj1Le16zjIdjKmaWiIrUT4A4yYEulkLjujiOZNQOWF6wl7ZWKdG5B7E91brrv/pqP2TBe8axRS8RsBsrqv/n4LxvGTr4AZrLpMX6k/lCo28QBNzjrtq3+iGhxC6BQNg7qdgOh7CUYRt08O6LCtPnCgM9t2cXfrkYKlj3aoSeRqYozjxH2t4cfxdtgZczvX7tLPosLuFnU8OtiLPuB46PeSx+YbYcRGERzSy/vsvvbcGeABNpWC7/0rOvAdPMZiFYY+UARXWNd+Oegi1RQle7yPAU/FjHIA4qOcLhj3u6hyxb6UFwiHlzRXqE9GWawze6TkXLMbbkX7EJ6Mcr6FU3EBFpJ/5EatItzb8XyatyQrW5bZm+FZ4eXOwCNnwDTUkjOkYH1UlW2BcBSuodbVT+3avM8nBRSmOEaXXtrNpFF7ROCsdKIHUQpWq/2aDy9ccKnnClcLtdlvjCO5WAODXrk=
file: notify-redis-linux
skip_cleanup: true
on:
repo: icewind1991/notify-redis
tags: true

1148
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,21 @@
name = "notify-redis"
version = "0.1.0"
authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018"
[dependencies]
notify = "4.0.0"
redis = "0.8.0"
notify = "6.0"
notify-debouncer-full = "0.2.0"
redis = { version = "0.23", default-features = false }
time = { version = "0.3.22", features = ["serde", "formatting", "parsing"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
color-eyre = "0.6"
clap = { version = "4.3.9", features = ["derive"] }
[dev-dependencies]
rand = "0.8.5"
tempfile = "3"
[profile.release]
lto = true

View file

@ -1,9 +0,0 @@
all: target/x86_64-unknown-linux-musl/release/notify-redis
target/x86_64-unknown-linux-musl/release/notify-redis: Cargo.toml src/main.rs
docker run --rm -it -v "$(CURDIR):/home/rust/src" ekidd/rust-musl-builder cargo build --release
.PHONY: test
test: target/x86_64-unknown-linux-musl/release/notify-redis
./test.sh

View file

@ -1,11 +1,24 @@
# Notify Redis
[![Build Status](https://travis-ci.org/icewind1991/notify-redis.svg?branch=master)](https://travis-ci.org/icewind1991/notify-redis)
Push filesystem notifications into a redis list
## Getting the binary
There are 3 ways for getting the binary to run
- Grab a pre-compiled static binary from the [releases](https://github.com/icewind1991/notify-redis/releases) page.
- By running `nix build` to use docker to build a static binary (requires `nix`)
- By running `cargo build` (requires `rust`)
## Usage
```
notify-redis /path/to/watch redis://localhost list_name
```
The recorded filesystem events will be pushed to the configured list.
Details about how events are encoded can be found [here](https://github.com/icewind1991/nc-fs-events/)
Filesystem events are debounced and merge where applicable (e.g. `touch foo.txt`, `mv foo.txt bar.txt` will result in one write event for `bar.txt`)

129
flake.lock generated Normal file
View file

@ -0,0 +1,129 @@
{
"nodes": {
"cross-naersk": {
"inputs": {
"naersk": [
"naersk"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1687811683,
"narHash": "sha256-j0+0y2CBlwrbVkVEZajjAy9gdzHRNCq8hQTRe+QXTAQ=",
"owner": "icewind1991",
"repo": "cross-naersk",
"rev": "5e987fcf0521602914773016b173403d0fa873f9",
"type": "github"
},
"original": {
"owner": "icewind1991",
"repo": "cross-naersk",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1687709756,
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1687852486,
"narHash": "sha256-2rXkhKUVQxbVaC+TITPpILiy/dSbordOLs87eoWHYxA=",
"owner": "nix-community",
"repo": "naersk",
"rev": "df10963b956962913b693a638746a95d6c506404",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1687829761,
"narHash": "sha256-QRe1Y8SS3M4GeC58F/6ajz6V0ZLUVWX3ZAMgov2N3/g=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9790f3242da2152d5aa1976e3e4b8b414f4dd206",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-23.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"cross-naersk": "cross-naersk",
"flake-utils": "flake-utils",
"naersk": "naersk",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1688005946,
"narHash": "sha256-aEK0CNCIfE6ALQuztj86sl4PZUzMDnbp68r6I5YW+AE=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "2925988bbc95f94e7b2f822b914ac5612a636e93",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

90
flake.nix Normal file
View file

@ -0,0 +1,90 @@
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "nixpkgs/nixos-23.05";
naersk.url = "github:nix-community/naersk";
naersk.inputs.nixpkgs.follows = "nixpkgs";
rust-overlay.url = "github:oxalica/rust-overlay";
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
rust-overlay.inputs.flake-utils.follows = "flake-utils";
cross-naersk.url = "github:icewind1991/cross-naersk";
cross-naersk.inputs.nixpkgs.follows = "nixpkgs";
cross-naersk.inputs.naersk.follows = "naersk";
};
outputs = {
self,
nixpkgs,
flake-utils,
naersk,
rust-overlay,
cross-naersk,
}:
flake-utils.lib.eachDefaultSystem (
system: let
overlays = [(import rust-overlay)];
pkgs = import nixpkgs {
inherit system overlays;
};
lib = pkgs.lib;
cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;};
hostTarget = pkgs.hostPlatform.config;
targets = [
"x86_64-unknown-linux-musl"
"i686-unknown-linux-musl"
"armv7-unknown-linux-musleabihf"
"aarch64-unknown-linux-musl"
];
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src|tests)(/.*)?"];
nearskOpt = {
pname = "notify-redis";
root = src;
};
buildTarget = target: (cross-naersk'.buildPackage target) nearskOpt;
hostNaersk = cross-naersk'.hostNaersk;
in rec {
# `nix build`
packages = nixpkgs.lib.attrsets.genAttrs targets buildTarget // rec {
notify-redis = pkgs.callPackage (import ./package.nix) {};
default = notify-redis;
check = hostNaersk.buildPackage (nearskOpt // {
mode = "check";
});
clippy = hostNaersk.buildPackage (nearskOpt // {
mode = "clippy";
});
test = hostNaersk.buildPackage (nearskOpt // {
mode = "test";
nativeBuildInputs = [pkgs.redis];
overrideMain = x: x // {
preBuild = ''
redis-server &
export redisPID=$!
'';
postBuild = ''
kill $redisPID
'';
};
});
dockerImage = pkgs.dockerTools.buildImage {
name = "icewind1991/notify-redis";
tag = "latest";
copyToRoot = [notify-redis];
config = {
Cmd = ["${notify-redis}/bin/notify-redis"];
};
};
};
inherit targets;
# `nix develop`
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [rustc cargo bacon cargo-edit cargo-outdated clippy];
};
}
);
}

25
package.nix Normal file
View file

@ -0,0 +1,25 @@
{
rustPlatform,
lib,
}: let
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src|tests)(/.*)?"];
in
rustPlatform.buildRustPackage rec {
version = "0.2.1";
pname = "notify-redis";
inherit src;
cargoLock = {
lockFile = ./Cargo.lock;
};
doCheck = false;
meta = with lib; {
description = "Push filesystem notifications into a redis list";
homepage = "https://github.com/icewind1991/notify-redis";
license = licenses.mit;
platforms = platforms.linux;
};
}

105
src/lib.rs Normal file
View file

@ -0,0 +1,105 @@
use color_eyre::{eyre::WrapErr, Result};
use notify::event::{ModifyKind, RenameMode};
use notify::{EventKind, RecursiveMode, Watcher};
use notify_debouncer_full::{new_debouncer, DebouncedEvent};
use redis::{Client, Commands, Connection, IntoConnectionInfo};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "event")]
#[serde(rename_all = "snake_case")]
pub enum Event {
Modify {
path: PathBuf,
#[serde(with = "time::serde::iso8601")]
time: OffsetDateTime,
},
Move {
from: PathBuf,
to: PathBuf,
#[serde(with = "time::serde::iso8601")]
time: OffsetDateTime,
},
Delete {
path: PathBuf,
#[serde(with = "time::serde::iso8601")]
time: OffsetDateTime,
},
None,
}
impl From<DebouncedEvent> for Event {
fn from(event: DebouncedEvent) -> Self {
let now = OffsetDateTime::now_utc();
let elapsed = event.time.elapsed();
let time = now - elapsed;
let path_count = event.paths.len();
let mut paths = event.event.paths.into_iter();
match (event.event.kind, path_count) {
(EventKind::Modify(ModifyKind::Name(RenameMode::Both)), 2..) => Event::Move {
from: paths.next().unwrap(),
to: paths.next().unwrap(),
time,
},
(EventKind::Modify(_) | EventKind::Create(_), 1..) => Event::Modify {
path: paths.next().unwrap(),
time,
},
(EventKind::Remove(_), 1..) => Event::Delete {
path: paths.next().unwrap(),
time,
},
_ => Event::None,
}
}
}
pub fn watch(
path: impl AsRef<Path>,
redis_connect: impl IntoConnectionInfo,
redis_list: &str,
debounce: Duration,
) -> Result<()> {
let (tx, rx) = channel();
let mut watcher = new_debouncer(debounce, None, tx)?;
let client = Client::open(redis_connect).wrap_err("Invalid redis connection")?;
let mut con = client
.get_connection()
.wrap_err("Failed to open redis connection")?;
watcher
.watcher()
.watch(path.as_ref(), RecursiveMode::Recursive)?;
while let Ok(event) = rx.recv() {
for event in event.into_iter().flatten() {
push_event(event, &mut con, redis_list).wrap_err("Failed to send event to redis")?;
}
}
Ok(())
}
fn push_event(event: DebouncedEvent, con: &mut Connection, list: &str) -> Result<()> {
match format_event(event) {
Some(formatted_event) => {
println!("{}", formatted_event);
Ok(con.lpush(list, formatted_event)?)
}
None => Ok(()),
}
}
fn format_event(event: DebouncedEvent) -> Option<String> {
let event: Event = event.into();
match &event {
Event::None => None,
_ => serde_json::to_string(&event).ok(),
}
}

View file

@ -1,76 +1,28 @@
extern crate notify;
extern crate redis;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use redis::{Client, Commands, Connection, RedisResult};
use std::env;
use std::result::Result;
use std::sync::mpsc::channel;
use clap::Parser;
use color_eyre::Result;
use notify_redis::watch;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug)]
enum WatchError {
Notify(notify::Error),
Redis(redis::RedisError),
/// Push filesystem notifications into a redis list
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Folder to watch
path: PathBuf,
/// Redis connection string
redis_connect: String,
/// Redis list to push changes to
redis_list: String,
}
impl From<notify::Error> for WatchError {
fn from(err: notify::Error) -> WatchError {
WatchError::Notify(err)
}
}
impl From<redis::RedisError> for WatchError {
fn from(err: redis::RedisError) -> WatchError {
WatchError::Redis(err)
}
}
fn watch(path: String, redis_connect: String, redis_list: String) -> Result<(), WatchError> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
let client = Client::open(redis_connect.as_ref())?;
let con = client.get_connection()?;
watcher.watch(path, RecursiveMode::Recursive)?;
loop {
match rx.recv() {
Ok(event) => push_event(event, &con, &redis_list)?,
Err(e) => println!("watch error: {:?}", e),
}
}
}
fn push_event(event: DebouncedEvent, con: &Connection, list: &String) -> RedisResult<()> {
match format_event(event) {
Some(formatted_event) => {
println!("{}", formatted_event);
return con.lpush(list, formatted_event);
},
None => Ok(())
}
}
fn format_event(event: DebouncedEvent) -> Option<String> {
match event {
DebouncedEvent::Write(path) |
DebouncedEvent::Create(path) |
DebouncedEvent::Chmod(path) => Some(format!("write|{}", path.to_str()?)),
DebouncedEvent::Rename(from, to) => Some(format!("rename|{}|{}", from.to_str()?, to.to_str()?)),
DebouncedEvent::Remove(path) => Some(format!("remove|{}", path.to_str()?)),
_ => None
}
}
fn main() {
let args: Vec<_> = env::args().collect();
if args.len() == 4 {
if let Err(e) = watch(args[1].to_owned(), args[2].to_owned(), args[3].to_owned()) {
println!("error: {:?}", e)
}
} else {
println!("usage: {} <path> <redis_connect> <redis_list>", args[0])
}
fn main() -> Result<()> {
let args: Args = Args::parse();
watch(
args.path,
args.redis_connect,
&args.redis_list,
Duration::from_secs(2),
)?;
Ok(())
}

56
test.sh
View file

@ -1,56 +0,0 @@
#!/bin/bash
function assert_notify {
actual="$(redis-cli rpop notify)"
if [ "$actual" != "$1" ]
then
echo "'$actual' not equal to expected '$1'"
killall $(basename $bin)
exit 1
fi
}
mkdir -p test
rm test/*
redis-cli del notify
bin=$1;
: ${bin:="target/x86_64-unknown-linux-musl/release/notify-redis"}
echo "running tests with $bin"
$bin "$PWD/test" redis://localhost notify &
sleep 1
echo foo > test/foo.txt
sleep 2
assert_notify "write|$(pwd)/test/foo.txt"
assert_notify ''
mv test/foo.txt test/bar.txt
sleep 2
assert_notify "rename|$(pwd)/test/foo.txt|$(pwd)/test/bar.txt"
assert_notify ""
rm test/bar.txt
echo asd > test/bar.txt
sleep 2
assert_notify "write|$(pwd)/test/bar.txt"
assert_notify ""
rm test/bar.txt
sleep 2
echo asd > test/bar.txt
sleep 2
assert_notify "remove|$(pwd)/test/bar.txt"
assert_notify "write|$(pwd)/test/bar.txt"
assert_notify ""
killall $(basename $bin)

162
tests/tests.rs Normal file
View file

@ -0,0 +1,162 @@
use notify_redis::{watch, Event};
use redis::{Client, Commands, Connection, ConnectionInfo};
use std::fs::{remove_file, rename, write};
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
use tempfile::tempdir;
fn cleanup(redis: ConnectionInfo, list: &str) {
let client = Client::open(redis).unwrap();
let mut con = client.get_connection().unwrap();
con.del::<_, ()>(list).unwrap();
}
struct EventList {
redis: Connection,
list: String,
}
impl EventList {
fn new(redis: ConnectionInfo, list: &str) -> Self {
let client = Client::open(redis).unwrap();
let redis = client.get_connection().unwrap();
EventList {
redis,
list: list.into(),
}
}
fn next(&mut self) -> Option<Event> {
let raw: Option<String> = self.redis.rpop(&self.list, None).unwrap();
raw.map(|raw| serde_json::from_str(&raw).unwrap())
}
}
fn spawn_watch(
path: &Path,
redis_connect: ConnectionInfo,
list: &str,
) -> std::thread::JoinHandle<()> {
let path = path.to_path_buf();
let list = list.to_string();
std::thread::spawn(move || {
if let Err(e) = watch(path, redis_connect, &list, Duration::from_millis(1)) {
eprintln!("watch error {:#}", e);
}
})
}
#[test]
fn test_basic() {
let list = format!("notify_redis_test_{}", rand::random::<u16>());
let redis_connect: ConnectionInfo = "redis://localhost".parse().unwrap();
cleanup(redis_connect.clone(), &list);
let dir = tempdir().unwrap();
let mut event_list = EventList::new(redis_connect.clone(), &list);
spawn_watch(dir.path(), redis_connect.clone(), &list);
sleep(Duration::from_millis(10));
write(dir.path().join("foo.txt"), "foo").unwrap();
sleep(Duration::from_millis(10));
assert!(
matches!(event_list.next(), Some(Event::Modify {path ,..}) if path.ends_with("foo.txt"))
);
assert!(matches!(event_list.next(), None));
}
#[test]
fn test_rename_debounce() {
let list = format!("notify_redis_test_{}", rand::random::<u16>());
let redis_connect: ConnectionInfo = "redis://localhost".parse().unwrap();
cleanup(redis_connect.clone(), &list);
let dir = tempdir().unwrap();
let mut event_list = EventList::new(redis_connect.clone(), &list);
spawn_watch(dir.path(), redis_connect.clone(), &list);
sleep(Duration::from_millis(10));
write(dir.path().join("foo.txt"), "foo").unwrap();
rename(dir.path().join("foo.txt"), dir.path().join("bar.txt")).unwrap();
sleep(Duration::from_millis(10));
assert!(
matches!(event_list.next(), Some(Event::Modify {path ,..}) if path.ends_with("bar.txt"))
);
assert!(matches!(event_list.next(), None));
}
#[test]
fn test_rename() {
let list = format!("notify_redis_test_{}", rand::random::<u16>());
let redis_connect: ConnectionInfo = "redis://localhost".parse().unwrap();
cleanup(redis_connect.clone(), &list);
let dir = tempdir().unwrap();
let mut event_list = EventList::new(redis_connect.clone(), &list);
spawn_watch(dir.path(), redis_connect.clone(), &list);
sleep(Duration::from_millis(10));
write(dir.path().join("foo.txt"), "foo").unwrap();
sleep(Duration::from_millis(10));
rename(dir.path().join("foo.txt"), dir.path().join("bar.txt")).unwrap();
sleep(Duration::from_millis(10));
assert!(
matches!(event_list.next(), Some(Event::Modify {path ,..}) if path.ends_with("foo.txt"))
);
assert!(
matches!(event_list.next(), Some(Event::Move {from, to ,..}) if from.ends_with("foo.txt") && to.ends_with("bar.txt"))
);
assert!(matches!(dbg!(event_list.next()), None));
}
#[test]
fn test_delete() {
let list = format!("notify_redis_test_{}", rand::random::<u16>());
let redis_connect: ConnectionInfo = "redis://localhost".parse().unwrap();
cleanup(redis_connect.clone(), &list);
let dir = tempdir().unwrap();
let mut event_list = EventList::new(redis_connect.clone(), &list);
spawn_watch(dir.path(), redis_connect.clone(), &list);
sleep(Duration::from_millis(10));
write(dir.path().join("foo.txt"), "foo").unwrap();
sleep(Duration::from_millis(10));
remove_file(dir.path().join("foo.txt")).unwrap();
sleep(Duration::from_millis(10));
assert!(
matches!(event_list.next(), Some(Event::Modify {path ,..}) if path.ends_with("foo.txt"))
);
assert!(
matches!(event_list.next(), Some(Event::Delete {path ,..}) if path.ends_with("foo.txt"))
);
assert!(matches!(dbg!(event_list.next()), None));
}
#[test]
fn test_delete_debounce() {
let list = format!("notify_redis_test_{}", rand::random::<u16>());
let redis_connect: ConnectionInfo = "redis://localhost".parse().unwrap();
cleanup(redis_connect.clone(), &list);
let dir = tempdir().unwrap();
let mut event_list = EventList::new(redis_connect.clone(), &list);
spawn_watch(dir.path(), redis_connect.clone(), &list);
sleep(Duration::from_millis(10));
write(dir.path().join("foo.txt"), "foo").unwrap();
remove_file(dir.path().join("foo.txt")).unwrap();
sleep(Duration::from_millis(10));
assert!(matches!(dbg!(event_list.next()), None));
}

View file

@ -1,7 +0,0 @@
#!/bin/sh
echo "Building static binaries using ekidd/rust-musl-builder"
docker build -t build-"$1"-image .
docker run -it --name build-"$1" build-"$1"-image
docker cp build-"$1":/home/rust/src/target/x86_64-unknown-linux-musl/release/"$1" "$1-$2"
docker rm build-"$1"
docker rmi build-"$1"-image