├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── example ├── Cargo.toml └── src │ └── main.rs └── src ├── lib.rs └── wrap.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio-inotify" 3 | version = "0.4.1" 4 | authors = ["Lewin Bormann "] 5 | license = "MIT" 6 | keywords = ["inotify", "tokio", "async", "filesystem"] 7 | description = "Stream-based access to filesystem events." 8 | repository = "https://github.com/dermesser/tokio-inotify" 9 | documentation = "https://docs.rs/tokio-inotify" 10 | 11 | [dependencies] 12 | futures = "0.1" 13 | tokio = "0.1" 14 | mio = "0.6" 15 | inotify = "0.3" 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Lewin Bormann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tokio-inotify 2 | 3 | **NOTE: The `inotify` crate now offers a `Stream`-based API. It is recommended to use it directly.** 4 | 5 | [![crates.io](https://img.shields.io/crates/v/tokio-inotify.svg)](https://crates.io/crates/tokio-inotify) 6 | 7 | [Documentation](https://docs.rs/tokio-inotify/) 8 | 9 | The `tokio_inotify` crate enables the use of inotify file descriptors in the `tokio` framework. 10 | It builds on the [`inotify`](https://github.com/hannobraun/inotify-rs) crate by wrapping 11 | the `INotify` type into a new type called `AsyncINotify`, and implementing 12 | [`futures::stream::Stream`](http://alexcrichton.com/futures-rs/futures/stream/trait.Stream.html). 13 | 14 | This means that you can consume `inotify::Event`s from the `AsyncINotify` object and act on them. 15 | 16 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inot-test" 3 | version = "0.1.0" 4 | authors = ["Lewin Bormann "] 5 | 6 | [dependencies] 7 | futures = "0.1.1" 8 | tokio = "0.1" 9 | tokio-inotify = { path = ".." } 10 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate tokio; 3 | extern crate tokio_inotify; 4 | 5 | use std::path::Path; 6 | use std::env; 7 | 8 | use futures::stream::Stream; 9 | use futures::prelude::*; 10 | use tokio_inotify::AsyncINotify; 11 | 12 | fn home_dir() -> String { 13 | env::var("HOME").unwrap_or_else(|_| { 14 | env::var("USER") 15 | .map(|u| { 16 | let mut d = "/home/".to_string(); 17 | d.push_str(&u); 18 | d 19 | }) 20 | .unwrap() 21 | }) 22 | } 23 | 24 | fn main() { 25 | let inot = AsyncINotify::init().unwrap(); 26 | inot.add_watch( 27 | Path::new(&home_dir()), 28 | tokio_inotify::IN_CREATE | tokio_inotify::IN_DELETE, 29 | ).unwrap(); 30 | 31 | let show_events = inot.for_each(|ev| { 32 | tokio::spawn(futures::future::poll_fn(move || { 33 | if ev.is_create() { 34 | println!("created {:?}", ev.name); 35 | } else if ev.is_delete() { 36 | println!("deleted {:?}", ev.name); 37 | } 38 | Ok(Async::Ready(())) 39 | })); 40 | Ok(()) 41 | }).map_err(|_| ()); 42 | 43 | tokio::run(show_events); 44 | } 45 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate inotify; 3 | extern crate mio; 4 | extern crate tokio; 5 | 6 | mod wrap; 7 | 8 | pub use inotify::ffi::*; 9 | 10 | pub use wrap::AsyncINotify; 11 | -------------------------------------------------------------------------------- /src/wrap.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use inotify::wrapper::{Event, INotify, Watch}; 4 | use futures::{Async, Poll}; 5 | use futures::stream::Stream; 6 | use mio::event::Evented; 7 | use mio::{Poll as MioPoll, PollOpt, Ready, Token}; 8 | use mio::unix::EventedFd; 9 | use tokio::reactor::PollEvented2 as PollEvented; 10 | 11 | use std::io; 12 | use std::os::unix::io::RawFd; 13 | use std::path::Path; 14 | 15 | struct NonLifetimedEventedFd(RawFd); 16 | 17 | impl Evented for NonLifetimedEventedFd { 18 | fn register( 19 | &self, 20 | poll: &MioPoll, 21 | token: Token, 22 | interest: Ready, 23 | opts: PollOpt, 24 | ) -> io::Result<()> { 25 | let evfd = EventedFd(&self.0); 26 | evfd.register(poll, token, interest, opts) 27 | } 28 | 29 | fn reregister( 30 | &self, 31 | poll: &MioPoll, 32 | token: Token, 33 | interest: Ready, 34 | opts: PollOpt, 35 | ) -> io::Result<()> { 36 | let evfd = EventedFd(&self.0); 37 | evfd.reregister(poll, token, interest, opts) 38 | } 39 | 40 | fn deregister(&self, poll: &MioPoll) -> io::Result<()> { 41 | let evfd = EventedFd(&self.0); 42 | evfd.deregister(poll) 43 | } 44 | } 45 | 46 | /// Wraps an INotify object and provides asynchronous methods based on the inner object. 47 | pub struct AsyncINotify { 48 | inner: INotify, 49 | io: PollEvented, 50 | 51 | cached_events: Vec, 52 | } 53 | 54 | impl AsyncINotify { 55 | /// Create a new inotify stream on the loop behind `handle`. 56 | pub fn init() -> io::Result { 57 | AsyncINotify::init_with_flags(0) 58 | } 59 | 60 | /// Create a new inotify stream with the given inotify flags (`IN_NONBLOCK` or `IN_CLOEXEC`). 61 | pub fn init_with_flags(flags: i32) -> io::Result { 62 | let inotify = try!(INotify::init_with_flags(flags)); 63 | 64 | let pollev = PollEvented::new(NonLifetimedEventedFd(inotify.fd as RawFd)); 65 | 66 | Ok(AsyncINotify { 67 | inner: inotify, 68 | io: pollev, 69 | cached_events: Vec::new(), 70 | }) 71 | } 72 | 73 | /// Monitor `path` for the events in `mask`. For a list of events, see 74 | /// https://docs.rs/tokio-inotify/0.2.1/tokio_inotify/struct.AsyncINotify.html (items prefixed with 75 | /// "Event") 76 | pub fn add_watch(&self, path: &Path, mask: u32) -> io::Result { 77 | self.inner.add_watch(path, mask) 78 | } 79 | 80 | /// Remove an element currently watched. 81 | pub fn rm_watch(&self, watch: Watch) -> io::Result<()> { 82 | self.inner.rm_watch(watch) 83 | } 84 | 85 | /// Close the underlying file descriptor and remove it from the event loop. 86 | pub fn close(self) -> io::Result<()> { 87 | // FD is removed from loop by PollEvented::drop() 88 | self.inner.close() 89 | } 90 | } 91 | 92 | impl Stream for AsyncINotify { 93 | type Item = Event; 94 | type Error = io::Error; 95 | 96 | fn poll(&mut self) -> Poll, Self::Error> { 97 | // BUG-ish: This returns cached events in a reversed order. Usually, that shouldn't be a 98 | // problem though. 99 | if self.cached_events.len() > 0 { 100 | // Only register interest once we're down to the last cached event. 101 | if self.cached_events.len() == 1 { 102 | self.io.clear_read_ready(Ready::readable())?; 103 | } 104 | return Ok(Async::Ready(self.cached_events.pop())); 105 | } 106 | 107 | match self.io.poll_read_ready(Ready::readable()) { 108 | Ok(Async::NotReady) => { 109 | self.io.clear_read_ready(Ready::readable())?; 110 | return Ok(Async::NotReady); 111 | } 112 | Ok(Async::Ready(_)) => (), // proceed 113 | Err(e) => return Err(e), 114 | } 115 | 116 | // the inner fd is non-blocking by default (set in the inotify crate) 117 | let events = try!(self.inner.available_events()); 118 | 119 | // Only do vec operations if there are many events 120 | if events.len() < 1 { 121 | // If EWOULDBLOCK is returned, inotify returns an empty slice. Signal that we want 122 | // more. 123 | self.io.clear_read_ready(Ready::readable())?; 124 | Ok(Async::NotReady) 125 | } else if events.len() == 1 { 126 | self.io.clear_read_ready(Ready::readable())?; 127 | Ok(Async::Ready(Some(events[0].clone()))) 128 | } else { 129 | // events.len() > 1 130 | self.cached_events.extend_from_slice(&events[1..]); 131 | Ok(Async::Ready(Some(events[0].clone()))) 132 | } 133 | } 134 | } 135 | --------------------------------------------------------------------------------