Repository: hannobraun/inotify-rs (language: rust)

Issue:
Add separate API for adding/removing watches
Add a separate `Watches` struct that provides `add` and `remove` methods to replace `Inotify::add_watch`/`Inotify::rm_watch`. It should be possible to get an instance of this struct from `Inotify` and `EventStream` (for example via `watches` methods on each).

I think there should be no harm in making `Watches` completely independent (i.e. no need to borrow `Inotify` or `EventStream`) and make it `Clone`.

See https://github.com/hannobraun/inotify-rs/issues/112#issuecomment-850352465 and https://github.com/hannobraun/inotify-rs/issues/112#issuecomment-850418244 for more context.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index dc0ac82..f3d27f4 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,8 @@ fn main() {
         .expect("Failed to determine current directory");
 
     inotify
-        .add_watch(
+        .watches()
+        .add(
             current_dir,
             WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE,
         )
diff --git a/examples/stream.rs b/examples/stream.rs
index f2e6b88..03047c1 100644
--- a/examples/stream.rs
+++ b/examples/stream.rs
@@ -19,7 +19,7 @@ async fn main() -> Result<(), io::Error> {
 
     let dir = TempDir::new()?;
 
-    inotify.add_watch(dir.path(), WatchMask::CREATE | WatchMask::MODIFY)?;
+    inotify.watches().add(dir.path(), WatchMask::CREATE | WatchMask::MODIFY)?;
 
     thread::spawn::<_, Result<(), io::Error>>(move || {
         loop {
diff --git a/examples/watch.rs b/examples/watch.rs
index c86ff17..8bdeb7b 100644
--- a/examples/watch.rs
+++ b/examples/watch.rs
@@ -15,7 +15,8 @@ fn main() {
         .expect("Failed to determine current directory");
 
     inotify
-        .add_watch(
+        .watches()
+        .add(
             current_dir,
             WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE,
         )
diff --git a/src/events.rs b/src/events.rs
index 5b3b44e..9ea1ccc 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -63,26 +63,26 @@ impl<'a> Iterator for Events<'a> {
 /// An inotify event
 ///
 /// A file system event that describes a change that the user previously
-/// registered interest in. To watch for events, call [`Inotify::add_watch`]. To
+/// registered interest in. To watch for events, call [`Watches::add`]. To
 /// retrieve events, call [`Inotify::read_events_blocking`] or
 /// [`Inotify::read_events`].
 ///
-/// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch
+/// [`Watches::add`]: struct.Watches.html#method.add
 /// [`Inotify::read_events_blocking`]: struct.Inotify.html#method.read_events_blocking
 /// [`Inotify::read_events`]: struct.Inotify.html#method.read_events
 #[derive(Clone, Debug)]
 pub struct Event<S> {
     /// Identifies the watch this event originates from
     ///
-    /// This [`WatchDescriptor`] is equal to the one that [`Inotify::add_watch`]
+    /// This [`WatchDescriptor`] is equal to the one that [`Watches::add`]
     /// returned when interest for this event was registered. The
     /// [`WatchDescriptor`] can be used to remove the watch using
-    /// [`Inotify::rm_watch`], thereby preventing future events of this type
+    /// [`Watches::remove`], thereby preventing future events of this type
     /// from being created.
     ///
     /// [`WatchDescriptor`]: struct.WatchDescriptor.html
-    /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch
-    /// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch
+    /// [`Watches::add`]: struct.Watches.html#method.add
+    /// [`Watches::remove`]: struct.Watches.html#method.remove
     pub wd: WatchDescriptor,
 
     /// Indicates what kind of event this is
@@ -357,7 +357,7 @@ bitflags! {
         /// Watch was removed
         ///
         /// This event will be generated, if the watch was removed explicitly
-        /// (via [`Inotify::rm_watch`]), or automatically (because the file was
+        /// (via [`Watches::remove`]), or automatically (because the file was
         /// deleted or the file system was unmounted).
         ///
         /// See [`inotify_sys::IN_IGNORED`].
diff --git a/src/inotify.rs b/src/inotify.rs
index f434e78..f4a3c40 100644
--- a/src/inotify.rs
+++ b/src/inotify.rs
@@ -1,7 +1,5 @@
 use std::{
-    ffi::CString,
     io,
-    os::unix::ffi::OsStrExt,
     os::unix::io::{
         AsRawFd,
         FromRawFd,
@@ -31,6 +29,7 @@ use crate::util::read_into_buffer;
 use crate::watches::{
     WatchDescriptor,
     WatchMask,
+    Watches,
 };
 
 
@@ -121,157 +120,28 @@ impl Inotify {
         })
     }
 
-    /// Adds or updates a watch for the given path
+    /// Gets an interface that allows adding and removing watches.
+    /// See [`Watches::add`] and [`Watches::remove`].
     ///
-    /// Adds a new watch or updates an existing one for the file referred to by
-    /// `path`. Returns a watch descriptor that can be used to refer to this
-    /// watch later.
-    ///
-    /// The `mask` argument defines what kind of changes the file should be
-    /// watched for, and how to do that. See the documentation of [`WatchMask`]
-    /// for details.
-    ///
-    /// If this method is used to add a new watch, a new [`WatchDescriptor`] is
-    /// returned. If it is used to update an existing watch, a
-    /// [`WatchDescriptor`] that equals the previously returned
-    /// [`WatchDescriptor`] for that watch is returned instead.
-    ///
-    /// Under the hood, this method just calls [`inotify_add_watch`] and does
-    /// some trivial translation between the types on the Rust side and the C
-    /// side.
-    ///
-    /// # Attention: Updating watches and hardlinks
-    ///
-    /// As mentioned above, this method can be used to update an existing watch.
-    /// This is usually done by calling this method with the same `path`
-    /// argument that it has been called with before. But less obviously, it can
-    /// also happen if the method is called with a different path that happens
-    /// to link to the same inode.
-    ///
-    /// You can detect this by keeping track of [`WatchDescriptor`]s and the
-    /// paths they have been returned for. If the same [`WatchDescriptor`] is
-    /// returned for a different path (and you haven't freed the
-    /// [`WatchDescriptor`] by removing the watch), you know you have two paths
-    /// pointing to the same inode, being watched by the same watch.
-    ///
-    /// # Errors
-    ///
-    /// Directly returns the error from the call to
-    /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an
-    /// `io::Error`), without adding any error conditions of
-    /// its own.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// use inotify::{
-    ///     Inotify,
-    ///     WatchMask,
-    /// };
-    ///
-    /// let mut inotify = Inotify::init()
-    ///     .expect("Failed to initialize an inotify instance");
-    ///
-    /// # // Create a temporary file, so `add_watch` won't return an error.
-    /// # use std::fs::File;
-    /// # File::create("/tmp/inotify-rs-test-file")
-    /// #     .expect("Failed to create test file");
-    /// #
-    /// inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
-    ///     .expect("Failed to add file watch");
-    ///
-    /// // Handle events for the file here
-    /// ```
-    ///
-    /// [`inotify_add_watch`]: ../inotify_sys/fn.inotify_add_watch.html
-    /// [`WatchMask`]: struct.WatchMask.html
-    /// [`WatchDescriptor`]: struct.WatchDescriptor.html
+    /// [`Watches::add`]: struct.Watches.html#method.add
+    /// [`Watches::remove`]: struct.Watches.html#method.remove
+    pub fn watches(&self) -> Watches {
+        Watches::new(self.fd.clone())
+    }
+
+    /// Deprecated: use `Inotify.watches().add()` instead
+    #[deprecated = "use `Inotify.watches().add()` instead"]
     pub fn add_watch<P>(&mut self, path: P, mask: WatchMask)
         -> io::Result<WatchDescriptor>
         where P: AsRef<Path>
     {
-        let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
-
-        let wd = unsafe {
-            ffi::inotify_add_watch(
-                **self.fd,
-                path.as_ptr() as *const _,
-                mask.bits(),
-            )
-        };
-
-        match wd {
-            -1 => Err(io::Error::last_os_error()),
-            _  => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }),
-        }
+        self.watches().add(path, mask)
     }
 
-    /// Stops watching a file
-    ///
-    /// Removes the watch represented by the provided [`WatchDescriptor`] by
-    /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via
-    /// [`Inotify::add_watch`], or from the `wd` field of [`Event`].
-    ///
-    /// # Errors
-    ///
-    /// Directly returns the error from the call to [`inotify_rm_watch`].
-    /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given
-    /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance.
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// use inotify::Inotify;
-    ///
-    /// let mut inotify = Inotify::init()
-    ///     .expect("Failed to initialize an inotify instance");
-    ///
-    /// # // Create a temporary file, so `add_watch` won't return an error.
-    /// # use std::fs::File;
-    /// # let mut test_file = File::create("/tmp/inotify-rs-test-file")
-    /// #     .expect("Failed to create test file");
-    /// #
-    /// # // Add a watch and modify the file, so the code below doesn't block
-    /// # // forever.
-    /// # use inotify::WatchMask;
-    /// # inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
-    /// #     .expect("Failed to add file watch");
-    /// # use std::io::Write;
-    /// # write!(&mut test_file, "something\n")
-    /// #     .expect("Failed to write something to test file");
-    /// #
-    /// let mut buffer = [0; 1024];
-    /// let events = inotify
-    ///     .read_events_blocking(&mut buffer)
-    ///     .expect("Error while waiting for events");
-    ///
-    /// for event in events {
-    ///     inotify.rm_watch(event.wd);
-    /// }
-    /// ```
-    ///
-    /// [`WatchDescriptor`]: struct.WatchDescriptor.html
-    /// [`inotify_rm_watch`]: ../inotify_sys/fn.inotify_rm_watch.html
-    /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch
-    /// [`Event`]: struct.Event.html
-    /// [`Inotify`]: struct.Inotify.html
-    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
-    /// [`ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
+    /// Deprecated: use `Inotify.watches().remove()` instead
+    #[deprecated = "use `Inotify.watches().remove()` instead"]
     pub fn rm_watch(&mut self, wd: WatchDescriptor) -> io::Result<()> {
-        if wd.fd.upgrade().as_ref() != Some(&self.fd) {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "Invalid WatchDescriptor",
-            ));
-        }
-
-        let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) };
-        match result {
-            0  => Ok(()),
-            -1 => Err(io::Error::last_os_error()),
-            _  => panic!(
-                "unexpected return code from inotify_rm_watch ({})", result)
-        }
+        self.watches().remove(wd)
     }
 
     /// Waits until events are available, then returns them
diff --git a/src/lib.rs b/src/lib.rs
index 7375ee5..b037ae1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,14 +19,15 @@
 //! let mut inotify = Inotify::init()
 //!     .expect("Error while initializing inotify instance");
 //!
-//! # // Create a temporary file, so `add_watch` won't return an error.
+//! # // Create a temporary file, so `Watches::add` won't return an error.
 //! # use std::fs::File;
 //! # let mut test_file = File::create("/tmp/inotify-rs-test-file")
 //! #     .expect("Failed to create test file");
 //! #
 //! // Watch for modify and close events.
 //! inotify
-//!     .add_watch(
+//!     .watches()
+//!     .add(
 //!         "/tmp/inotify-rs-test-file",
 //!         WatchMask::MODIFY | WatchMask::CLOSE,
 //!     )
@@ -37,7 +38,7 @@
 //! # write!(&mut test_file, "something\n")
 //! #     .expect("Failed to write something to test file");
 //! #
-//! // Read events that were added with `add_watch` above.
+//! // Read events that were added with `Watches::add` above.
 //! let mut buffer = [0; 1024];
 //! let events = inotify.read_events_blocking(&mut buffer)
 //!     .expect("Error while reading events");
diff --git a/src/stream.rs b/src/stream.rs
index 7576738..aaad5c7 100644
--- a/src/stream.rs
+++ b/src/stream.rs
@@ -12,6 +12,7 @@ use tokio::io::unix::AsyncFd;
 use crate::events::{Event, EventOwned};
 use crate::fd_guard::FdGuard;
 use crate::util::read_into_buffer;
+use crate::watches::Watches;
 
 /// Stream of inotify events
 ///
@@ -39,6 +40,15 @@ where
             unused_bytes: 0,
         })
     }
+
+    /// Returns an instance of `Watches` to add and remove watches.
+    /// See [`Watches::add`] and [`Watches::remove`].
+    ///
+    /// [`Watches::add`]: struct.Watches.html#method.add
+    /// [`Watches::remove`]: struct.Watches.html#method.remove
+    pub fn watches(&self) -> Watches {
+        Watches::new(self.fd.get_ref().0.clone())
+    }
 }
 
 impl<T> Stream for EventStream<T>
diff --git a/src/watches.rs b/src/watches.rs
index 81ff005..d8a39e3 100644
--- a/src/watches.rs
+++ b/src/watches.rs
@@ -1,11 +1,18 @@
 use std::{
+    cmp::Ordering,
+    ffi::CString,
     hash::{
         Hash,
         Hasher,
     },
-    cmp::Ordering,
+    io,
     os::raw::c_int,
-    sync::Weak,
+    os::unix::ffi::OsStrExt,
+    path::Path,
+    sync::{
+        Arc,
+        Weak,
+    },
 };
 
 use inotify_sys as ffi;
@@ -16,12 +23,12 @@ use crate::fd_guard::FdGuard;
 bitflags! {
     /// Describes a file system watch
     ///
-    /// Passed to [`Inotify::add_watch`], to describe what file system events
+    /// Passed to [`Watches::add`], to describe what file system events
     /// to watch for, and how to do that.
     ///
     /// # Examples
     ///
-    /// `WatchMask` constants can be passed to [`Inotify::add_watch`] as is. For
+    /// `WatchMask` constants can be passed to [`Watches::add`] as is. For
     /// example, here's how to create a watch that triggers an event when a file
     /// is accessed:
     ///
@@ -33,12 +40,12 @@ bitflags! {
     /// #
     /// # let mut inotify = Inotify::init().unwrap();
     /// #
-    /// # // Create a temporary file, so `add_watch` won't return an error.
+    /// # // Create a temporary file, so `Watches::add` won't return an error.
     /// # use std::fs::File;
     /// # File::create("/tmp/inotify-rs-test-file")
     /// #     .expect("Failed to create test file");
     /// #
-    /// inotify.add_watch("/tmp/inotify-rs-test-file", WatchMask::ACCESS)
+    /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::ACCESS)
     ///    .expect("Error adding watch");
     /// ```
     ///
@@ -52,11 +59,11 @@ bitflags! {
     /// # };
     /// #
     /// # let mut inotify = Inotify::init().unwrap();
-    /// inotify.add_watch("/tmp/", WatchMask::CREATE | WatchMask::DELETE)
+    /// inotify.watches().add("/tmp/", WatchMask::CREATE | WatchMask::DELETE)
     ///    .expect("Error adding watch");
     /// ```
     ///
-    /// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch
+    /// [`Watches::add`]: struct.Watches.html#method.add
     pub struct WatchMask: u32 {
         /// File was accessed
         ///
@@ -275,14 +282,184 @@ bitflags! {
 }
 
 
+/// Interface for adding and removing watches
+#[derive(Clone, Debug)]
+pub struct Watches {
+    pub(crate) fd: Arc<FdGuard>,
+}
+
+impl Watches {
+    /// Init watches with an inotify file descriptor
+    pub(crate) fn new(fd: Arc<FdGuard>) -> Self {
+        Watches {
+            fd,
+        }
+    }
+
+    /// Adds or updates a watch for the given path
+    ///
+    /// Adds a new watch or updates an existing one for the file referred to by
+    /// `path`. Returns a watch descriptor that can be used to refer to this
+    /// watch later.
+    ///
+    /// The `mask` argument defines what kind of changes the file should be
+    /// watched for, and how to do that. See the documentation of [`WatchMask`]
+    /// for details.
+    ///
+    /// If this method is used to add a new watch, a new [`WatchDescriptor`] is
+    /// returned. If it is used to update an existing watch, a
+    /// [`WatchDescriptor`] that equals the previously returned
+    /// [`WatchDescriptor`] for that watch is returned instead.
+    ///
+    /// Under the hood, this method just calls [`inotify_add_watch`] and does
+    /// some trivial translation between the types on the Rust side and the C
+    /// side.
+    ///
+    /// # Attention: Updating watches and hardlinks
+    ///
+    /// As mentioned above, this method can be used to update an existing watch.
+    /// This is usually done by calling this method with the same `path`
+    /// argument that it has been called with before. But less obviously, it can
+    /// also happen if the method is called with a different path that happens
+    /// to link to the same inode.
+    ///
+    /// You can detect this by keeping track of [`WatchDescriptor`]s and the
+    /// paths they have been returned for. If the same [`WatchDescriptor`] is
+    /// returned for a different path (and you haven't freed the
+    /// [`WatchDescriptor`] by removing the watch), you know you have two paths
+    /// pointing to the same inode, being watched by the same watch.
+    ///
+    /// # Errors
+    ///
+    /// Directly returns the error from the call to
+    /// [`inotify_add_watch`][`inotify_add_watch`] (translated into an
+    /// `io::Error`), without adding any error conditions of
+    /// its own.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use inotify::{
+    ///     Inotify,
+    ///     WatchMask,
+    /// };
+    ///
+    /// let mut inotify = Inotify::init()
+    ///     .expect("Failed to initialize an inotify instance");
+    ///
+    /// # // Create a temporary file, so `Watches::add` won't return an error.
+    /// # use std::fs::File;
+    /// # File::create("/tmp/inotify-rs-test-file")
+    /// #     .expect("Failed to create test file");
+    /// #
+    /// inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
+    ///     .expect("Failed to add file watch");
+    ///
+    /// // Handle events for the file here
+    /// ```
+    ///
+    /// [`inotify_add_watch`]: ../inotify_sys/fn.inotify_add_watch.html
+    /// [`WatchMask`]: struct.WatchMask.html
+    /// [`WatchDescriptor`]: struct.WatchDescriptor.html
+    pub fn add<P>(&mut self, path: P, mask: WatchMask)
+                        -> io::Result<WatchDescriptor>
+        where P: AsRef<Path>
+    {
+        let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
+
+        let wd = unsafe {
+            ffi::inotify_add_watch(
+                **self.fd,
+                path.as_ptr() as *const _,
+                mask.bits(),
+            )
+        };
+
+        match wd {
+            -1 => Err(io::Error::last_os_error()),
+            _  => Ok(WatchDescriptor{ id: wd, fd: Arc::downgrade(&self.fd) }),
+        }
+    }
+
+    /// Stops watching a file
+    ///
+    /// Removes the watch represented by the provided [`WatchDescriptor`] by
+    /// calling [`inotify_rm_watch`]. [`WatchDescriptor`]s can be obtained via
+    /// [`Watches::add`], or from the `wd` field of [`Event`].
+    ///
+    /// # Errors
+    ///
+    /// Directly returns the error from the call to [`inotify_rm_watch`].
+    /// Returns an [`io::Error`] with [`ErrorKind`]`::InvalidInput`, if the given
+    /// [`WatchDescriptor`] did not originate from this [`Inotify`] instance.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use inotify::Inotify;
+    ///
+    /// let mut inotify = Inotify::init()
+    ///     .expect("Failed to initialize an inotify instance");
+    ///
+    /// # // Create a temporary file, so `Watches::add` won't return an error.
+    /// # use std::fs::File;
+    /// # let mut test_file = File::create("/tmp/inotify-rs-test-file")
+    /// #     .expect("Failed to create test file");
+    /// #
+    /// # // Add a watch and modify the file, so the code below doesn't block
+    /// # // forever.
+    /// # use inotify::WatchMask;
+    /// # inotify.watches().add("/tmp/inotify-rs-test-file", WatchMask::MODIFY)
+    /// #     .expect("Failed to add file watch");
+    /// # use std::io::Write;
+    /// # write!(&mut test_file, "something\n")
+    /// #     .expect("Failed to write something to test file");
+    /// #
+    /// let mut buffer = [0; 1024];
+    /// let events = inotify
+    ///     .read_events_blocking(&mut buffer)
+    ///     .expect("Error while waiting for events");
+    /// let mut watches = inotify.watches();
+    ///
+    /// for event in events {
+    ///     watches.remove(event.wd);
+    /// }
+    /// ```
+    ///
+    /// [`WatchDescriptor`]: struct.WatchDescriptor.html
+    /// [`inotify_rm_watch`]: ../inotify_sys/fn.inotify_rm_watch.html
+    /// [`Watches::add`]: struct.Watches.html#method.add
+    /// [`Event`]: struct.Event.html
+    /// [`Inotify`]: struct.Inotify.html
+    /// [`io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
+    /// [`ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
+    pub fn remove(&mut self, wd: WatchDescriptor) -> io::Result<()> {
+        if wd.fd.upgrade().as_ref() != Some(&self.fd) {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                "Invalid WatchDescriptor",
+            ));
+        }
+
+        let result = unsafe { ffi::inotify_rm_watch(**self.fd, wd.id) };
+        match result {
+            0  => Ok(()),
+            -1 => Err(io::Error::last_os_error()),
+            _  => panic!(
+                "unexpected return code from inotify_rm_watch ({})", result)
+        }
+    }
+}
+
+
 /// Represents a watch on an inode
 ///
-/// Can be obtained from [`Inotify::add_watch`] or from an [`Event`]. A watch
+/// Can be obtained from [`Watches::add`] or from an [`Event`]. A watch
 /// descriptor can be used to get inotify to stop watching an inode by passing
-/// it to [`Inotify::rm_watch`].
+/// it to [`Watches::remove`].
 ///
-/// [`Inotify::add_watch`]: struct.Inotify.html#method.add_watch
-/// [`Inotify::rm_watch`]: struct.Inotify.html#method.rm_watch
+/// [`Watches::add`]: struct.Watches.html#method.add
+/// [`Watches::remove`]: struct.Watches.html#method.remove
 /// [`Event`]: struct.Event.html
 #[derive(Clone, Debug)]
 pub struct WatchDescriptor{


Test patch:
diff --git a/tests/main.rs b/tests/main.rs
index 4ded4a2..181371a 100644
--- a/tests/main.rs
+++ b/tests/main.rs
@@ -28,7 +28,7 @@ fn it_should_watch_a_file() {
     let (path, mut file) = testdir.new_file();
 
     let mut inotify = Inotify::init().unwrap();
-    let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
+    let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
 
     write_to(&mut file);
 
@@ -50,7 +50,7 @@ async fn it_should_watch_a_file_async() {
     let (path, mut file) = testdir.new_file();
 
     let mut inotify = Inotify::init().unwrap();
-    let watch = inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
+    let watch = inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
 
     write_to(&mut file);
 
@@ -74,6 +74,37 @@ async fn it_should_watch_a_file_async() {
     assert!(num_events > 0);
 }
 
+#[cfg(feature = "stream")]
+#[tokio::test]
+async fn it_should_watch_a_file_from_eventstream_watches() {
+    let mut testdir = TestDir::new();
+    let (path, mut file) = testdir.new_file();
+
+    let mut inotify = Inotify::init().unwrap();
+
+    let mut buffer = [0; 1024];
+
+    use futures_util::StreamExt;
+    let stream = inotify.event_stream(&mut buffer[..]).unwrap();
+
+    let watch = stream.watches().add(&path, WatchMask::MODIFY).unwrap();
+    write_to(&mut file);
+
+    let events = stream
+        .take(1)
+        .collect::<Vec<_>>()
+        .await;
+
+    let mut num_events = 0;
+    for event in events {
+        if let Ok(event) = event {
+            assert_eq!(watch, event.wd);
+            num_events += 1;
+        }
+    }
+    assert!(num_events > 0);
+}
+
 #[test]
 fn it_should_return_immediately_if_no_events_are_available() {
     let mut inotify = Inotify::init().unwrap();
@@ -88,7 +119,7 @@ fn it_should_convert_the_name_into_an_os_str() {
     let (path, mut file) = testdir.new_file();
 
     let mut inotify = Inotify::init().unwrap();
-    inotify.add_watch(&path.parent().unwrap(), WatchMask::MODIFY).unwrap();
+    inotify.watches().add(&path.parent().unwrap(), WatchMask::MODIFY).unwrap();
 
     write_to(&mut file);
 
@@ -109,7 +140,7 @@ fn it_should_set_name_to_none_if_it_is_empty() {
     let (path, mut file) = testdir.new_file();
 
     let mut inotify = Inotify::init().unwrap();
-    inotify.add_watch(&path, WatchMask::MODIFY).unwrap();
+    inotify.watches().add(&path, WatchMask::MODIFY).unwrap();
 
     write_to(&mut file);
 
@@ -129,13 +160,13 @@ fn it_should_not_accept_watchdescriptors_from_other_instances() {
     let mut testdir = TestDir::new();
     let (path, _) = testdir.new_file();
 
-    let mut inotify = Inotify::init().unwrap();
-    let _ = inotify.add_watch(&path, WatchMask::ACCESS).unwrap();
+    let inotify = Inotify::init().unwrap();
+    let _ = inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
 
-    let mut second_inotify = Inotify::init().unwrap();
-    let wd2 = second_inotify.add_watch(&path, WatchMask::ACCESS).unwrap();
+    let second_inotify = Inotify::init().unwrap();
+    let wd2 = second_inotify.watches().add(&path, WatchMask::ACCESS).unwrap();
 
-    assert_eq!(inotify.rm_watch(wd2).unwrap_err().kind(), ErrorKind::InvalidInput);
+    assert_eq!(inotify.watches().remove(wd2).unwrap_err().kind(), ErrorKind::InvalidInput);
 }
 
 #[test]
@@ -143,16 +174,18 @@ fn watch_descriptors_from_different_inotify_instances_should_not_be_equal() {
     let mut testdir = TestDir::new();
     let (path, _) = testdir.new_file();
 
-    let mut inotify_1 = Inotify::init()
+    let inotify_1 = Inotify::init()
         .unwrap();
-    let mut inotify_2 = Inotify::init()
+    let inotify_2 = Inotify::init()
         .unwrap();
 
     let wd_1 = inotify_1
-        .add_watch(&path, WatchMask::ACCESS)
+        .watches()
+        .add(&path, WatchMask::ACCESS)
         .unwrap();
     let wd_2 = inotify_2
-        .add_watch(&path, WatchMask::ACCESS)
+        .watches()
+        .add(&path, WatchMask::ACCESS)
         .unwrap();
 
     // As far as inotify is concerned, watch descriptors are just integers that
@@ -173,12 +206,13 @@ fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() {
     // old one.
     // This is quite likely, but it doesn't happen every time. Therefore we may
     // need a few tries until we find two instances where that is the case.
-    let (wd_1, mut inotify_2) = loop {
-        let mut inotify_1 = Inotify::init()
+    let (wd_1, inotify_2) = loop {
+        let inotify_1 = Inotify::init()
             .unwrap();
 
         let wd_1 = inotify_1
-            .add_watch(&path, WatchMask::ACCESS)
+            .watches()
+            .add(&path, WatchMask::ACCESS)
             .unwrap();
         let fd_1 = inotify_1.as_raw_fd();
 
@@ -194,7 +228,8 @@ fn watch_descriptor_equality_should_not_be_confused_by_reused_fds() {
     };
 
     let wd_2 = inotify_2
-        .add_watch(&path, WatchMask::ACCESS)
+        .watches()
+        .add(&path, WatchMask::ACCESS)
         .unwrap();
 
     // The way we engineered this situation, both `WatchDescriptor` instances
@@ -231,6 +266,33 @@ fn it_should_implement_raw_fd_traits_correctly() {
     }
 }
 
+#[test]
+fn it_should_watch_correctly_with_a_watches_clone() {
+    let mut testdir = TestDir::new();
+    let (path, mut file) = testdir.new_file();
+
+    let mut inotify = Inotify::init().unwrap();
+    let mut watches1 = inotify.watches();
+    let mut watches2 = watches1.clone();
+    let watch1 = watches1.add(&path, WatchMask::MODIFY).unwrap();
+    let watch2 = watches2.add(&path, WatchMask::MODIFY).unwrap();
+
+    // same path and same Inotify should return same descriptor
+    assert_eq!(watch1, watch2);
+
+    write_to(&mut file);
+
+    let mut buffer = [0; 1024];
+    let events = inotify.read_events_blocking(&mut buffer).unwrap();
+
+    let mut num_events = 0;
+    for event in events {
+        assert_eq!(watch2, event.wd);
+        num_events += 1;
+    }
+    assert!(num_events > 0);
+}
+
 
 struct TestDir {
     dir: TempDir,