mirror of
https://github.com/syncthing/notify.git
synced 2026-06-07 08:57:32 +00:00
cmd/notify: rework notify command
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
notify [](https://godoc.org/github.com/rjeczalik/notify) [](https://travis-ci.org/rjeczalik/notify "inotify") [](https://travis-ci.org/rjeczalik/notify "FSEvents") [](https://travis-ci.org/rjeczalik/notify "kqueue") [](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [](https://coveralls.io/r/rjeczalik/notify?branch=master)
|
||||
[](https://travis-ci.org/rjeczalik/notify "inotify") [](https://travis-ci.org/rjeczalik/notify "FSEvents") [](https://travis-ci.org/rjeczalik/notify "kqueue") [](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [](https://coveralls.io/r/rjeczalik/notify?branch=master)
|
||||
======
|
||||
|
||||
### notify [](https://godoc.org/github.com/rjeczalik/notify)
|
||||
|
||||
Filesystem event notification library on steroids. (under active development)
|
||||
|
||||
*Installation*
|
||||
@@ -9,6 +11,40 @@ Filesystem event notification library on steroids. (under active development)
|
||||
~ $ go get -u github.com/rjeczalik/notify
|
||||
```
|
||||
|
||||
*Documentation*
|
||||
*Documentation*
|
||||
|
||||
[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
|
||||
|
||||
### cmd/notify [](https://godoc.org/github.com/rjeczalik/notify)
|
||||
|
||||
Listens on filesystem changes and forwards received events to user-defined handlers.
|
||||
|
||||
*Installation*
|
||||
|
||||
```
|
||||
~ $ go get -u github.com/rjeczalik/notify/cmd/notify
|
||||
```
|
||||
|
||||
*Documentation*
|
||||
|
||||
[godoc.org/github.com/rjeczalik/notify/cmd/notify](https://godoc.org/github.com/rjeczalik/notify/cmd/notify)
|
||||
|
||||
*Usage*
|
||||
|
||||
```bash
|
||||
~ $ notify -c 'echo "Hello from handler! (event={{.Event}}, path={{.Path}})"'
|
||||
2015/02/17 01:17:40 received notify.Create: "/Users/rjeczalik/notify.tmp"
|
||||
Hello from handler! (event=create, path=/Users/rjeczalik/notify.tmp)
|
||||
2015/02/17 01:18:13 received notify.Write: "/Users/rjeczalik/notify.tmp"
|
||||
Hello from handler! (event=write, path=/Users/rjeczalik/notify.tmp)
|
||||
```
|
||||
```bash
|
||||
~ $ cat > handler <<EOF
|
||||
> echo "Hello from handler! (event={{.Event}}, path={{.Path}})"
|
||||
> EOF
|
||||
~ $ notify -f handler
|
||||
2015/02/17 01:22:26 received notify.Create: "/Users/rjeczalik/notify.tmp"
|
||||
Hello from handler! (event=create, path=/Users/rjeczalik/notify.tmp)
|
||||
2015/02/17 01:22:26 received notify.Remove: "/Users/rjeczalik/notify.tmp"
|
||||
Hello from handler! (event=remove, path=/Users/rjeczalik/notify.tmp)
|
||||
```
|
||||
|
||||
+220
-30
@@ -2,41 +2,194 @@
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Command notify listens on filesystem changes and forwards received mapping to
|
||||
// user-defined handlers.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// usage: notify [-c command] [-f script file] [path]...
|
||||
//
|
||||
// The -c flag registers a command handler, which uses the syntax
|
||||
// of package template. Notify passes struct to the template,
|
||||
// splits produced string into command and args, and runs it using
|
||||
// exec.Command(). Additionaly the path and event type values are
|
||||
// accesible to the process via NOTIFY_PATH and NOTIFY_EVENT
|
||||
// environment variables.
|
||||
//
|
||||
// The struct being passed to the template is:
|
||||
//
|
||||
// type Event struct {
|
||||
// Path string
|
||||
// Event string
|
||||
// }
|
||||
//
|
||||
// Values for the Event field are:
|
||||
//
|
||||
// - create
|
||||
// - remove
|
||||
// - rename
|
||||
// - write
|
||||
//
|
||||
// The -t flag registers a file handler, which works similary
|
||||
// to the -c handler. The only difference the template is read from
|
||||
// the given file instead of the command line.
|
||||
//
|
||||
// The path argument tells notify which director or directories to
|
||||
// listen on. By default notify listens recursively in current working
|
||||
// directory.
|
||||
//
|
||||
// If no handler is specified notify prints each event to os.Stdout.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"text/template"
|
||||
|
||||
"github.com/rjeczalik/notify"
|
||||
)
|
||||
|
||||
const usage = "usage: notify path [EVENT...]"
|
||||
const usage = `usage: notify [-c command] [-f script file] [path]...
|
||||
|
||||
const tformat = "2006-01-02 15:04:05.0000"
|
||||
Listens on filesystem changes and forwards received mapping to
|
||||
user-defined handlers.
|
||||
|
||||
var event = map[string]notify.Event{
|
||||
"all": notify.All,
|
||||
"create": notify.Create,
|
||||
"delete": notify.Remove,
|
||||
"write": notify.Write,
|
||||
"move": notify.Rename,
|
||||
The -c flag registers a command handler, which uses the syntax
|
||||
of package template. Notify passes struct to the template,
|
||||
splits produced string into command and args, and runs it using
|
||||
exec.Command(). Additionaly the path and event type values are
|
||||
accesible to the process via NOTIFY_PATH and NOTIFY_EVENT
|
||||
environment variables.
|
||||
|
||||
The struct being passed to the template is:
|
||||
|
||||
type Event struct {
|
||||
Path string
|
||||
Event string
|
||||
}
|
||||
|
||||
Values for the Event field are:
|
||||
|
||||
- create
|
||||
- remove
|
||||
- rename
|
||||
- write
|
||||
|
||||
The -t flag registers a file handler, which works similary
|
||||
to the -c handler. The only difference the template is read from
|
||||
the given file instead of the command line.
|
||||
|
||||
The path argument tells notify which director or directories to
|
||||
listen on. By default notify listens recursively in current working
|
||||
directory.
|
||||
|
||||
If no handler is specified notify prints each event to os.Stdout.`
|
||||
|
||||
var (
|
||||
file string
|
||||
command string
|
||||
paths = []string{"." + string(os.PathSeparator) + "..."}
|
||||
env = newenv()
|
||||
)
|
||||
|
||||
var mapping = map[notify.Event]string{
|
||||
notify.Create: "create",
|
||||
notify.Remove: "remove",
|
||||
notify.Rename: "rename",
|
||||
notify.Write: "write",
|
||||
}
|
||||
|
||||
func parse(s []string) (e []notify.Event) {
|
||||
if len(s) == 0 {
|
||||
return []notify.Event{notify.All}
|
||||
}
|
||||
for _, s := range s {
|
||||
event, ok := event[strings.ToLower(s)]
|
||||
if !ok {
|
||||
die("invalid event: " + s)
|
||||
func newenv() func(Event) []string {
|
||||
env := os.Environ()
|
||||
for i, s := range env {
|
||||
s = strings.ToLower(s)
|
||||
if strings.Contains(s, "NOTIFY_PATH=") || strings.Contains(s, "NOTIFY_EVENT=") {
|
||||
env[i], env = env[len(env)-1], env[:len(env)-1]
|
||||
}
|
||||
e = append(e, event)
|
||||
}
|
||||
return
|
||||
env = append(env, "", "")
|
||||
return func(e Event) []string {
|
||||
s := make([]string, len(env))
|
||||
copy(s, env)
|
||||
s[len(s)-1] = "NOTIFY_EVENT=" + e.Event
|
||||
s[len(s)-2] = "NOTIFY_PATH=" + e.Path
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// Handler TODO(rjeczalik)
|
||||
type Handler struct {
|
||||
tmpl *template.Template
|
||||
env []string
|
||||
}
|
||||
|
||||
// NewHandler TODO(rjeczalik)
|
||||
func NewHandler(text string) (*Handler, error) {
|
||||
tmpl, err := template.New("main.Handler").Parse(text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := &Handler{
|
||||
tmpl: tmpl,
|
||||
env: env(Event{}),
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Run TODO(rjeczalik)
|
||||
func (h *Handler) Run(e Event) error {
|
||||
var buf bytes.Buffer
|
||||
if err := h.tmpl.Execute(&buf, e); err != nil {
|
||||
return err
|
||||
}
|
||||
s := buf.String()
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", s)
|
||||
default:
|
||||
cmd = exec.Command("/bin/sh", "-c", s)
|
||||
}
|
||||
h.env[len(h.env)-1] = "NOTIFY_EVENT=" + e.Event
|
||||
h.env[len(h.env)-2] = "NOTIFY_PATH=" + e.Path
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = h.env
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Daemon TODO(rjeczalik)
|
||||
func (h *Handler) Daemon() chan<- Event {
|
||||
c := make(chan Event)
|
||||
go func() {
|
||||
for e := range c {
|
||||
if err := h.Run(e); err != nil {
|
||||
log.Println("handler error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
// Event TODO(rjeczalik)
|
||||
type Event struct {
|
||||
Path string
|
||||
Event string
|
||||
}
|
||||
|
||||
// NewEvent TODO(rjeczalik)
|
||||
func NewEvent(ei notify.EventInfo) Event {
|
||||
return Event{
|
||||
Path: ei.Path(),
|
||||
Event: mapping[ei.Event()],
|
||||
}
|
||||
}
|
||||
|
||||
func die(v interface{}) {
|
||||
@@ -44,20 +197,57 @@ func die(v interface{}) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
die(usage)
|
||||
func init() {
|
||||
flag.CommandLine.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, usage)
|
||||
}
|
||||
for _, path := range strings.Split(os.Args[1], string(os.PathListSeparator)) {
|
||||
ch := make(chan notify.EventInfo, 10)
|
||||
if err := notify.Watch(path, ch, parse(os.Args[2:])...); err != nil {
|
||||
flag.StringVar(&file, "f", "", "script file to execute on received event")
|
||||
flag.StringVar(&command, "c", "", "command to run on received event")
|
||||
flag.Parse()
|
||||
if flag.NArg() != 0 {
|
||||
paths = flag.Args()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var handlers []*Handler
|
||||
if command != "" {
|
||||
h, err := NewHandler(command)
|
||||
if err != nil {
|
||||
die(err)
|
||||
}
|
||||
go func(path string) {
|
||||
for ei := range ch {
|
||||
fmt.Printf("[%v] [%s] Event: %v\n", time.Now().Format(tformat), path, ei)
|
||||
handlers = append(handlers, h)
|
||||
}
|
||||
if file != "" {
|
||||
p, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
die(err)
|
||||
}
|
||||
h, err := NewHandler(string(p))
|
||||
if err != nil {
|
||||
die(err)
|
||||
}
|
||||
handlers = append(handlers, h)
|
||||
}
|
||||
var run []chan<- Event
|
||||
for _, h := range handlers {
|
||||
run = append(run, h.Daemon())
|
||||
}
|
||||
c := make(chan notify.EventInfo, 1)
|
||||
for _, path := range paths {
|
||||
if err := notify.Watch(path, c, notify.All); err != nil {
|
||||
die(err)
|
||||
}
|
||||
}
|
||||
for ei := range c {
|
||||
log.Println("received", ei)
|
||||
e := NewEvent(ei)
|
||||
for _, run := range run {
|
||||
select {
|
||||
case run <- e:
|
||||
default:
|
||||
log.Println("event dropped due to slow handler")
|
||||
}
|
||||
}(path)
|
||||
}
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// BUG(rjeczalik): Currently notify does not automagically set a watch for newly
|
||||
// BUG(rjeczalik): Notify does not automagically set a watch for newly
|
||||
// created directory within recursively watched path for inotify and kqueue. (#5)
|
||||
|
||||
// BUG(rjeczalik): Currently notify does not gracefully handle rewatching directories,
|
||||
// BUG(rjeczalik): Notify does not gracefully handle rewatching directories,
|
||||
// that were deleted but their watchpoints were not cleaned by the user. (#69)
|
||||
|
||||
// BUG(ppknap): Linux(inotify) does not currently support watcher behavior masks
|
||||
|
||||
Reference in New Issue
Block a user