Coder Social home page Coder Social logo

wkf / hawk Goto Github PK

View Code? Open in Web Editor NEW
184.0 6.0 17.0 1.78 MB

Clojure file and directory watcher using the JDK 7 java.nio.file.WatchService and Barbary WatchService

Home Page: https://wkf.github.com/hawk

License: Eclipse Public License 1.0

Clojure 19.28% HTML 0.66% JavaScript 78.41% Java 1.64%

hawk's Introduction

Hawk

Gitter

Hawk

A Clojure library designed to watch files and directories.

Like most clojure file watchers, hawk wraps the JDK 7 java.nio.file.WatchService API. This works great when Java has a native implementation for your platform of choice. However, this is not the case on OS X, so hawk also wraps the Barbary WatchService to provide performant monitoring even if you're using a mac. An appropriate implementation is chosen automatically, so all you have to do is install hawk and relax.

Hawk also features:

  • A facility to handle "stateful" watches
  • The ability to filter watches, and some handy built-in filters
  • A name that is also a kind of bird

Installation

To install, add the following dependency to your project.clj file:

Clojars Project


Usage

To get started with using hawk, require hawk.core in your project:

(ns hawk.sample
    (:require [hawk.core :as hawk]))

Simple Watches

To start a simple watch:

(hawk/watch! [{:paths ["src/main/hawk"]
               :handler (fn [ctx e]
                          (println "event: " e)
                          (println "context: " ctx)
                          ctx)}])

The :handler function is passed both the group's context value and the event being handled, and is expected to return the new context value. The event hash has the following structure:

{:kind :create           ;; the event kind, one of: #{:create :modify :delete}
 :file #java.io.File{} } ;; the (canonicalized) affected file

Filtered Watches

To start a simple filtered watch, use :filter:

(hawk/watch! [{:paths ["src/main/hawk"]
               :filter hawk/file?
               :handler (fn [_ _]
                          (println "look ma, just files!"))}])

The :filter function is also passed both the group's context and event being filtered. Hawk has some built-in filters, like hawk/file?, but any predicate function will work.

Stateful Watches

To start a stateful watch, use :context to initialize the state, and then just return the updated state from the :handler. In this example, the context is initialized to 1 when the watch is started, and then incremented whenever an event is handled:

(hawk/watch! [{:paths ["src/main/hawk"]
               :context (constantly 1)
               :handler (fn [ctx _] (inc ctx))}])

The :context function is passed only the group's current context, and is expected to return the new context value.

Hawk supports multiple watches with a single call to watch!. watch! accepts an arbitrary number of arrays, which can hold an arbitrary number of watch specs. Every spec in an an array will share the same state, and events are processed sequentially. Every array gets its own state, and events are processed (potentially) in parallel:

(hawk/watch!
    ;; here we pass 2 groups to watch!
    [{:paths ["src/main/hawk"]
      ;; for the first group, context is set to 1...
      :context (constantly 1)
      :handler (fn [ctx _]
                (println "I'm always first!")
                (inc ctx))}
    {:paths ["src/test/hawk"]
     ;; ...and then incremented to 2 by the second watch in the group
     :context (fn [ctx] (inc ctx))
     :handler (fn [_ _]
                (println "I'm always second place."))}]
   ;; here is the second watch group
   [{:paths ["src/main/hawk"]
     ;; its context (3) is totally separate from the above group
     :context (constantly 3)
     :handler (fn [_ _]
                (println "I'm also always first!"))}])

Polling Watches

There are cases (such as within virtual environments) where the operating system will not receive events when a file has changed. Hawk provides a polling watcher as a fallback mechanism for handling these cases:

(hawk/watch! {:watcher :polling}
             [{:paths ["src/main/hawk"]
               :handler (fn [ctx e]
                          (println "event: " e)
                          (println "context: " ctx)
                          ctx)}])

You may also provide a :sensitivity argument of :high (the default), :medium, or :low depending on how often you want polling to occur.

Stopping Watches

To stop a watch, use hawk/stop!:

(let [watcher (hawk/watch! [...])]
  (hawk/stop! watcher))

Happy watching!


License

Copyright (c) 2015 Will Farrell

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

hawk's People

Contributors

dmohs avatar e-k-m avatar gitter-badger avatar smee avatar wkf avatar yogthos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

hawk's Issues

JDK 8

Hi! Any plans to release the same API but for JDK 8?

Windows behaviour

Hi, I do not know if I am doing something wrong, but I am unable to work under Windows operating system with library.

I have created simple watcher:

(hawk/watch! 
  [{:paths   ["C:\\TestHawk"]
               
    :handler (fn [ctx e]
 (println "event: " e)
 ctx)}]))

After that I have created 2 folder, one into another, but second one is still registered under first "Temp folder" but this folder does not exists anymore.

My log:

event:  {:kind :create, :file #object[java.io.File 0x48ad13e7 C:\TestHawk\New folder]}
event:  {:kind :delete, :file #object[java.io.File 0x3ca75ebd C:\TestHawk\New folder]}
event:  {:kind :create, :file #object[java.io.File 0x2253733d C:\TestHawk\test-folder01]}
event:  {:kind :create, :file #object[java.io.File 0x2c949907 C:\TestHawk\New folder\New folder]}
event:  {:kind :modify, :file #object[java.io.File 0x3cc6ed65 C:\TestHawk\test-folder01]}
event:  {:kind :delete, :file #object[java.io.File 0x1c54e331 C:\TestHawk\New folder\New folder]}
event:  {:kind :create, :file #object[java.io.File 0x28ab184d C:\TestHawk\New folder\test-folder02]}
event:  {:kind :modify, :file #object[java.io.File 0x1a0004b0 C:\TestHawk\test-folder01]}

Maybe I am missing something or doing something wrong. Under mac is everything OK.

watch specific event

I have the following

(defn init
  []
  (export-pages)
  (hawk/watch! [{:paths ["src/app2/templates" "resources/templates"]
               :handler (fn [ctx e]

                          (export-pages))}]))

is there a way to watch for a specific event? i.e. {:kind :modify}

Reflection warnings

Version: [hawk "0.2.11"]

Thank you for this library! I'm trying this library with GraalVM and I see these reflection warnings. They probably have to be fixed to make it work for GraalVM.

Reflection warning, hawk/core.clj:16:30 - reference to field isFile can't be resolved.
Reflection warning, hawk/core.clj:21:18 - reference to field toPath can't be resolved.
Reflection warning, hawk/core.clj:22:18 - reference to field getParentFile can't be resolved.
Reflection warning, hawk/core.clj:22:18 - reference to field toPath can't be resolved.
Reflection warning, hawk/core.clj:33:25 - reference to field toPath on java.lang.Object can't be resolved.
Reflection warning, hawk/core.clj:35:29 - call to method startsWith can't be resolved (target class is unknown).
Reflection warning, hawk/core.clj:47:22 - call to method compareTo can't be resolved (target class is unknown).
Reflection warning, hawk/core.clj:55:27 - call to method startsWith can't be resolved (target class is unknown).
Reflection warning, hawk/core.clj:95:3 - reference to field interrupt can't be resolved.
Reflection warning, hawk/core.clj:95:3 - reference to field join can't be resolved.
Reflection warning, hawk/core.clj:100:3 - reference to field isFile on java.lang.Object can't be resolved.
Reflection warning, hawk/core.clj:103:3 - reference to field isDirectory on java.lang.Object can't be resolved.```

Change detection fails in docker environment

project.clj

(defproject hawktest "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.7.0"] [hawk "0.2.5"]]
  :source-paths ["."]
  :main main)

main.clj

(ns main
  (:require [hawk.core :as hawk]))

(defn -main
  [& args]
  (println "Hello world!")
  (hawk/watch! [{:paths ["."]
                 :handler (fn [ctx e]
                            (println "event: " e)
                            (println "contenxt: " ctx)
                            ctx)}]))

If I do a lein run and then touch main.clj, hawk detects the change and prints output. However, if I run like so:

docker run --name lein --rm -it -w /w -v "$PWD":/w -v "$HOME"/.m2:/root/.m2 clojure lein run

then touch main.clj does nothing. docker exec -it lein ls -l main.clj before and after the touch shows that the file modified time was changed.

Silently fails for file in cwd

If file is specified in the current working dir to watch like: "foo.edn", then process will not watch this group at all. I think this is because .getParentFile is called on this, which will return null. This would work if you first did .getCanonicalFile, as it would be the full path which does have a parent.

Hawk does not detect file changes in WSL-2

Hawk is not detecting file changes in Linux running in WSL-2

I am running Clojure 1.10.1 in Debian in WSL 2

uname -a:

Linux ... 4.19.104-microsoft-standard #1 SMP Wed Feb 19 06:37:35 UTC 2020 x86_64 GNU/Linux

Licensing issue

Hawk currently depends on the Barbary WatchService. They don't advertise it very clearly on their website, but it's under the GPL v2 because it includes OpenJDK code - see here for details. This means that it can't be used in Hawk since the GPL v2 is incompatible with the EPL, and would be virally transmitted to Hawk's users anyway. There's also a comment in the thread that makes it pretty clear that it's not actively maintained.

There's an alternative clean room implementation here (linked in that thread) which is under ASL. Unfortunately it's unclear how mature it is, and the API is also different. I'm going to investigate it a bit for my own use, but in the meantime sadly the best option for Hawk is to remove the Barbary dependency.

If file does not exist when watcher is started, does not watch

Hello Will,

I picked this library among others because it supports watching single files rather than directories (unlike other libraries like clojure-watch or juxt/dirwatch). However, I ran into an issue:

If I start a watcher on a single file that does not exist, it does not notice when the file is created.
But if I start it when the file exists, it notices deletion and creation.
Is this intended behavior?

Thanks in advance.

Moving directories causes wrong paths in File objects

I've reproduced the problem on Linux with the following directory structure (mkdir -p A/{B,C}):

+---A
    +---B
    \---C

Now watch dir A and run the following commands:

cd A
mkdir B/X
mv B/X/ C/
touch C/X/hello

Expected result:
File object in create event should use the path .../A/C/X/hello

Actual result:
File object in create event uses the path .../A/B/X/hello (Notice the B instead of a C).

Events no longer received after delete event

Hello,
I am using the watcher and noticed that I do not receive events after a delete event takes place.

E.G. The delete event occurs:
Event: {:kind :delete, :file #object[java.io.File 0x6d7ec7bb /private/tmp/sw/migrations/waiting/bffia]}

When I add a file, no events are detected. This is happening on MacOS and Linux. Any suggestions?

Get exception with barbarysoftware loading

After installing hawk per instruction, I'm getting following exception starting REPL on Mac OS X 10.10.2
java version "1.7.0_72"

Any suggestions?

Exception in thread "main" java.lang.ExceptionInInitializerError
at clojure.main.(main.java:20)
Caused by: java.lang.UnsupportedClassVersionError: com/barbarysoftware/watchservice/WatchService : Unsupported major.minor version 52.0, compiling:(hawk/core.clj:1:1)
at clojure.lang.Compiler.load(Compiler.java:7142)
at clojure.lang.RT.loadResourceScript(RT.java:370)
at clojure.lang.RT.loadResourceScript(RT.java:361)
at clojure.lang.RT.load(RT.java:440)
at clojure.lang.RT.load(RT.java:411)
at clojure.core$load$fn__5066.invoke(core.clj:5641)
at clojure.core$load.doInvoke(core.clj:5640)

recursive works on creating a watcher, but not for new directory additions

Hi, just playing around with hawk, learning clojure, etc.

I noticed that if I add a directory, the watcher previously set no longer watches the new directory

For example, if I do:

(hawk/watch! [{:paths ["/tmp/darktable_exported"]
               :handler (fn [ctx e]
                          (println "event: " e)
                          (println "context: " ctx)
                          ctx)}])

and do:

mkdir /tmp/darktable_exported/August_2016

I see in the repl:

event:  {:file #object[java.io.File 0x1c5661ba /tmp/darktable_exported/August_2016], :kind :create}
context:  {}

But then when I do:

touch /tmp/darktable_exported/August_2016/1.jpg

I see nothing in the repl.

If I restart the watcher, it will see the 1.jpg addition:

boot.user=> event:  {:file #object[java.io.File 0x2c8a60ac /tmp/darktable_exported/August_2016/1.jpg], :kind :create}
context:  {}
event:  {:file #object[java.io.File 0x173395d8 /tmp/darktable_exported/August_2016/1.jpg], :kind :modify}
context:  {}

So one fix from my end is to just check if the addition is a directory, and if it is, then toggle my watcher off and on. I also read in one of the other directory watching implementations that there is some sort of bug with recursively watching new directories, so actually fixing this within hawk is likely well beyond my freshly-minted clojure skill.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.