Filesystem event notification library on steroids.
Documentation
godoc.org/github.com/rjeczalik/notify
Installation
~ $ go get -u github.com/rjeczalik/notify
Projects using notify
File system event notification library on steroids.
License: MIT License
Filesystem event notification library on steroids.
Documentation
godoc.org/github.com/rjeczalik/notify
Installation
~ $ go get -u github.com/rjeczalik/notify
Projects using notify
Few unknowns:
notify/notifyutil
and hook somehow from there into notify.r
runtime which would allow for controlling itWhen underlying implementation is able to capture more events than test expects, it fails, which is logical. Instead the test should capture all the events, and after the exec
's are done it should compare the results.
--- FAIL: TestRuntimeWatcher-4 (0.01 seconds)
watcher_test.go:68: want EventInfo{Event: notify.Create, Name: github.com/rjeczalik/fs/fs_test.go, IsDir: false}; got EventInfo{Event: notify.IN_OPEN, Name: /tmp/notify052186543, IsDir:true}
FAIL
FAIL github.com/rjeczalik/notify 0.012s
Rewatch
and RecursiveRewatch
should support two more transitions.
Currently passing equal paths and eventsets to both is a nop. Instead it should change the watch state to non-recursive for the former and to recursive for the latter.
// modifies watch under "path" to be non-recursive
watcher.Rewatch("path", Create, Create)
// modifies watch under "path" to be recursive
watcher.RecursiveRewatch("path", "path", Create, Create)
https://travis-ci.org/yaey8Hee/Oong7Pha/builds/46892003
167 --- FAIL: TestNotifyExample (3.39 seconds)
168 testing_test.go:645: unexpected dangling events: [notify.Create, "/Users/travis/gopath/src/github.com/rjeczalik/notify/tmptree706295898/src/github.com/rjeczalik/which/.which.go.swo"]
notify.Error(ch) error
:ch := make(chan notify.EventInfo)
notify.Watch("/home/pknap/.secretporndir", ch, notify.Delete)
switch ei, ok := <-ch; {
case ei.Event() == notify.Delete:
fmt.Printf("Who deleted my %s file?;/\n", ei.Name())
case !ok:
fmt.Println("wtf: %v", notify.Error(ch))
}
[edit] The 1 option can look better:
ch := make(chan notify.EventInfo)
notify.Watch("/home/pknap/.secretporndir", ch, notify.Delete)
switch ei, ok := <-ch; ei.Event() {
case notify.Delete:
fmt.Printf("Who deleted my %s file?;/\n", ei.Name())
default:
if !ok {
fmt.Println("wtf: %v", notify.Error(ch))
}
}
type notifyError error
func (e *notifyError) Event() Event { ... }
func (e *notifyError) IsDir() bool { ... }
func (e *notifyError) Name() string { ... }
func (e *notifyError) Sys() interface{} { ... }
func (e *notifyError) Err() error { ... } // Extra method:>
then:
ch := make(chan notify.EventInfo)
notify.Watch("/home/pknap/rottenbodies", ch, notify.Delete)
switch ei := <-ch; ei.Event() {
case notify.Delete:
fmt.Printf("Who deleted my %s file?;/\n", ei.Name())
default: // need to have all registered events in case statement or check `ei.Err() != nil` after `default`
fmt.Println("wtf: %v", ei.Err())
}
Other ideas?
~ $ go test -run TestNotifyExample
PASS
ok github.com/rjeczalik/notify 2.121s
~ $ export NOTIFY_TMP=/tmp/notify
~ $ go test -run TestNotifyExample
--- FAIL: TestNotifyExample-4 (3.04s)
testing_test.go:605: Watch(/tmp/notify869446946/src/github.com/pblaszczyk/qttu, 0xc20805cd20, [notify.Write])=path is not being watched
testing_test.go:211: notify_test.go:44: ExpectNotifyEvents did not receive any of the expected events [Event(notify.Write)@src/github.com/rjeczalik/fs/fs.go] after 2s (i=0)
FAIL
exit status 1
FAIL github.com/rjeczalik/notify 3.072s
Question @pblaszczyk @ppknap how do you see expected Watcher behaviour? TL;DR is should it be forgiving (this is mostly what stdlib does) or strict? Examples:
Watcher.Watch
return error when called with the same path or act as nop?Watcher.Rewatch
return error when called with mismatched oldevent
(e.g. a9499fe#diff-959fa682b45de8bfd370a7aef8059650R73), ignore the oldevent
or act as nop?This is mainly for documentation purposes, as Tree
is not going to use your Watcher
s in that way. It may be good to define that behaviour when we would go for having every Watcher
as a separate package.
// This comment is TODO
{watcher,event}_inotify.go
code (review?)The common least denominator for functionality across all Watcher
implementations is watching a directory, instead of watching a file and/or directory.
(Just to quickly remind: runtime uses platform-specific implementation for managing watch-points. Those implementations have different functionalities on different platforms and in order to reflect that there are couple of "extension" interfaces (traits), which are used to provide functionality like editing an existing watch-point (Rewatcher
) or watching directory recursively (RecursiveWatcher
). Runtime is initialised with a value of a Watcher
type (base type) (NewRuntimeWatcher
), and inspects whether given watcher does implement any of the traits, providing it's own implementation in case it's not.)
If it would make things simplier, I can split Watcher
into DirWatcher
and FileWatcher
interfaces, making the former as base type and the latter as yet another trait. So we would have:
type DirWatcher interface {
DirWatch(path string, e Event) error
DirUnwatch(path string) error
Dispatch(c chan<- EventInfo, stop <-chan struct{})
}
// If not implemented by a based type, Runtime will
// emulate it by grouping calls to DirWatch.
type FileWatcher interface {
FileWatch(path string, e Event) error
FileUnwatch(path string) error
}
// If not implemented by a based type, Runtime will
// emulate it by calling Unwatch followed by Watch.
type Rewatcher interface {
Rewatch(path string, old, new Event)
}
// If not implemented by a based type, Runtime will
// emulate it by calling RecursiveUnwatch followed by RecursiveWatch.
type RecursiveRewatcher interface {
RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event)
}
// If not implemented by a based type, Runtime will
// emulate it by multiple DirWatch calls.
type RecursiveWatcher interface {
RecursiveWatch(path string, e Event)
RecursiveUnwatch(path string)
}
Asking, just to confirm it would simplify anything. In order to retain behaviour for existing implementations, that do not distinguish dir/file paths, it would be as simple as:
func (i *impl) DirWatch(path string, e Event) error {
return i.watch(path, e)
}
func (i *impl) FileWatch(path string, e Event) error {
return i.watch(path, e)
}
// ... and so on
notify.Delete
-> notify.Remove
(because os.Remove
)
notify.Move
-> notify.Rename
(because os.Rename
)
And we're still before release, so it's not too late for that.
Do you agree? @pblaszczyk @ppknap
User watches a file for write events only, file gets deleted, os watcher gets removed, file eventually get created and then written, user gets the write event buts does not know it's a write on a new file.
Delete
event on recursive watchpoint under WindowsIf a directory being the root of recurisve watchpoint gets deleted, Windows does not create an event for this. The solution is to watch parent directory non-recursively for the delete event.
...
https://ci.appveyor.com/project/rjeczalik/notify-246/build/91
=== RUN TestRecursiveTree
panic: runtime error: send on closed channel
goroutine 25 [running]:
runtime.panic(0x5ac540, 0x6db87e)
c:/go/src/pkg/runtime/panic.c:279 +0x11f
github.com/rjeczalik/notify.(*readdcw).send(0xc0820085c0, 0xc08203a0c0, 0x1, 0x1)
c:/projects/src/github.com/rjeczalik/notify/watcher_readdcw.go:408 +0x11b
github.com/rjeczalik/notify.(*readdcw).loopevent(0xc0820085c0, 0x22, 0xc082054600)
c:/projects/src/github.com/rjeczalik/notify/watcher_readdcw.go:389 +0x283
github.com/rjeczalik/notify.(*readdcw).loop(0xc0820085c0)
c:/projects/src/github.com/rjeczalik/notify/watcher_readdcw.go:336 +0x14c
created by github.com/rjeczalik/notify.(*readdcw).lazyinit
c:/projects/src/github.com/rjeczalik/notify/watcher_readdcw.go:307 +0x15f
The dispatch channel's lifetime has not changed, yet apparently readdcw
issues a dangling send. @ppknap reassign back to me if it turns out to be broken on my side.
The watcher should implement RecursiveWatcher
.
// issue description TODO
unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal 0xb code=0x1 addr=0xb01dfacedebac1e pc=0x402857a]
goroutine 22 [running]:
runtime.throw(0x42c2ed3)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/panic.c:520 +0x69 fp=0x43afe98 sp=0x43afe80
runtime.sigpanic()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/os_darwin.c:457 +0x13f fp=0x43afeb0 sp=0x43afe98
runtime.findnull(0x1b00000000043c20)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/string.goc:22 +0x1a fp=0x43afec0 sp=0x43afeb0
runtime.gostring(0x43aff18, 0x1b00000000043c20)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/string.goc:65 +0x27 fp=0x43afef8 sp=0x43afec0
github.com/rjeczalik/notify._Cfunc_GoString(0x1b00000000043c20, 0xc20805a120, 0x1a)
github.com/rjeczalik/notify/_test/_obj_test/_cgo_defun.c:14 +0x31 fp=0x43aff10 sp=0x43afef8
github.com/rjeczalik/notify.gocallback(0x440f790, 0xc20801a380, 0x2, 0x5000dd0, 0x43c4000, 0x43c3000)
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/fsevents_cgo.go:33 +0x98 fp=0x43aff78 sp=0x43aff10
----- stack segment boundary -----
runtime.cgocallbackg1()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/cgocall.c:275 +0xb9 fp=0x43b3f08 sp=0x43b3eb0
runtime.cgocallbackg()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/cgocall.c:247 +0x6b fp=0x43b3f30 sp=0x43b3f08
runtime.cgocallback_gofunc(0x4004dbd, 0x4001420, 0x43b3fa8)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/asm_amd64.s:786 +0x69 fp=0x43b3f40 sp=0x43b3f30
runtime.asmcgocall(0x4001420, 0x43b3fa8) [27/150]
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/asm_amd64.s:691 +0x3a fp=0x43b3f48 sp=0x43b3f40
runtime.cgocall(0x4001420, 0x43b3fa8)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/cgocall.c:143 +0xfd fp=0x43b3f90 sp=0x43b3f48
github.com/rjeczalik/notify._Cfunc_CFRunLoopRun(0x0)
github.com/rjeczalik/notify/_test/_obj_test/_cgo_defun.c:80 +0x31 fp=0x43b3fa8 sp=0x43b3f90
runtime.goexit()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/proc.c:1445 fp=0x43b3fb0 sp=0x43b3fa8
created by github.com/rjeczalik/notify.(*runloop).schedule
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/fsevents_cgo.go:48 +0x5e
goroutine 16 [chan receive]:
testing.RunTests(0x42190d0, 0x42c24e0, 0x19, 0x19, 0x1)
/usr/local/gvm/gos/go1.3.3/src/pkg/testing/testing.go:505 +0x923
testing.Main(0x42190d0, 0x42c24e0, 0x19, 0x19, 0x42c5b20, 0x0, 0x0, 0x42c5b20, 0x0, 0x0)
/usr/local/gvm/gos/go1.3.3/src/pkg/testing/testing.go:435 +0x84
main.main()
github.com/rjeczalik/notify/_test/_testmain.go:97 +0x9c
https://travis-ci.org/rjeczalik/notify/builds/48553299
runtime.panic(0x186820, 0x20833ddd0)
/usr/local/Cellar/go/1.3.1/libexec/src/pkg/runtime/panic.c:279 +0xf5
github.com/rjeczalik/notify.(*kqueue).monitor(0x208373110)
/Users/travis/src/github.com/rjeczalik/notify/watcher_kqueue.go:162 +0x9a0
created by github.com/rjeczalik/notify.newWatcher
/Users/travis/src/github.com/rjeczalik/notify/watcher_kqueue.go:27 +0x12e
@pblaszczyk Does it qualifies for a fix or should I add dispatching Error events? If the latter, the event is already in place so it's just matter of dispatching it.
almostNow
only for initializing new streamskFSEventStreamEventFlagEventIdsWrapped
event update all event ids// This comment is TODO
Currently in order to hint the notify
it should watch recursively one need to append notify.Recursive
event, like:
notify.Watch("/home/notify", c, notify.Create | notify.Recursive)
And yes, notify.Recursive
is not an event. So if you think the idea of having it among other events is retarted, we need to pick an alternative, which is to introduce another function:
notify.WatchRecursive
notify.WatchRecursive
has the same signature and works the same as notify.Watch
, with the only difference if path to watch is a directory, it'll be watched recursively
pros: cleaner API with not having notify.Recursive
as an event
const: can't tell from notify.EventInfo
if event was triggered by a recursive watch (not sure if needed)
fmt.Stringer
// This comment is TODO
When the same chan is used to watch both watchpoints, the child and parent one, the latter will get overwritten with inactive
flag coming from the child.
Quick fix would be to drop the inactive
flag for this chan, however this would require maintaining temporary unique set of user chans for each dispatch to ensure single event is not dispatched multiple times to the same channel.
The proper fix would be to rework recursiveTree
to not story inactive watchpoints altogether with active ones. In order to not hack the node
struct, best place would be (node).Child[""].Watch
, as empty-string keys are never traversed.
--- FAIL: TestWatcherShadowedWriteCreate (1.11 seconds)
testing_test.go:211: watcher_fsevents_test.go:100: ExpectAny received an event which does not match any of the expected ones (i=5): want one of [¬ify.Call{F:"", C:(chan notify.EventInfo)(nil), P:"src/github.com/rjeczalik/fs/.fs.go.swp", NP:"", E:0x1000, NE:0x0, S:interface {}(nil)}]; got notify.Delete,
For the following filesystem actions:
1 $ touch file
2 $ echo XD > file
3 $ echo XD > file
4 $ rm file
5 $ touch file
6 $ echo XD > file
FSEvents reports: event set
(event set after strip
)
Create | FSEventsIsFile
(Create | FSEventsIsFile
)Create | Write | FSEventsInodeMetaMod | FSEventsIsFile
(Write | FSEventsInodeMetaMod | FSEventsIsFile
)Create | Write | FSEventsIsFile | FSEventsInodeMetaMod
(Write | FSEventsIsFile | FSEventsInodeMetaMod
)Write | Create | Delete | FSEventsInodeMetaMod | FSEventsIsFile
(Delete | FSEventsIsFile
)Create | Delete | Write | FSEventsInodeMetaMod | FSEventsIsFile
(Create | Write | FSEventsInodeMetaMod | FSEventsIsFile
)Create | Delete | Write | FSEventsInodeMetaMod | FSEventsIsFile
(Delete | FSEventsIsFile
)// This comment is TODO
// This comment is TODO
Output:
F:\src\github.com\rjeczalik\notify>go test
--- FAIL: TestNotifyExample (2.58 seconds)
testing_test.go:580: Watch(testdata\vfs.txt590602367\src\github.com\rjeczalik\fs, 0x10c96420, [notify.Write])=Niepoprawna funkcja.
testing_test.go:580: Watch(testdata\vfs.txt590602367\src\github.com\pblaszczyk\qttu\..., 0x10c964d0, [notify.Create])=Niepoprawna funkcja.
testing_test.go:580: Watch(testdata\vfs.txt590602367\src\github.com\pblaszczyk\qttu, 0x10c96420, [notify.Write])=Niepoprawna funkcja.
testing_test.go:580: Watch(testdata\vfs.txt590602367\src\github.com\rjeczalik\fs\cmd\..., 0x10c96580, [notify.Delete])=Niepoprawna funkcja.
testing_test.go:190: notify_test.go:44: ExpectNotifyEvents did not receive any of the expected events [Event(notify.Write)@src\github.com\rjeczalik\fs\fs.go] after 2s (i=0)
Just FYI @pblaszczyk, probably it's just broken for OS X.
Steps to reproduce (sorry, no test yet).
~ $ go install github.com/rjeczalik/fs/cmd/mktree github.com/rjeczalik/fs/cmd/gotree
~ $ go install github.com/rjeczalik/notify/cmd/notify
~ $ # run in different terminal: notify $HOME/...
~ $ mkdir -p /tmp/1/2/3
~ $ touch /tmp/1/2.txt
~ $ touch /tmp/1/2/3.txt
~ $ touch /tmp/1/2/3/.txt
~ $ gotree /tmp/1 | mktree -o .
Panics with:
panic: permission denied
goroutine 20 [running]:
runtime.panic(0xe20a0, 0xd)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/panic.c:279 +0xf5
github.com/rjeczalik/notify.func·009(0x22081e94f0, 0x20824bd60, 0x0, 0x0)
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go:143 +0x244
github.com/rjeczalik/notify.(*kqueue).walk(0x2081fc330, 0x208216100, 0x3a, 0x22083a0f28, 0x0, 0x0)
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go:299 +0x155
github.com/rjeczalik/notify.(*kqueue).monitor(0x2081fc330)
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go:149 +0x911
created by github.com/rjeczalik/notify.(*kqueue).Dispatch
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go:376 +0x41
goroutine 16 [chan receive]:
main.main()
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/cmd/notify/main.go:49 +0x1b0
goroutine 19 [finalizer wait, 1 minutes]:
runtime.park(0x14ef0, 0x19b810, 0x19ab89)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/proc.c:1369 +0x89
runtime.parkunlock(0x19b810, 0x19ab89)
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/proc.c:1385 +0x3b
runfinq()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/mgc0.c:2644 +0xcf
runtime.goexit()
/usr/local/gvm/gos/go1.3.3/src/pkg/runtime/proc.c:1445
goroutine 21 [select, 1 minutes]:
github.com/rjeczalik/notify.(*Tree).loopdispatch(0x2081f2280, 0x2081eb200)
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/tree.go:179 +0x478
created by github.com/rjeczalik/notify.NewTree
/Users/rjeczalik/Workspace/src/github.com/rjeczalik/notify/tree.go:217 +0x1c6
// This comment is TODO
If a channel is registered only for Write events, the Remove events from fsnotify would get erroneously reported to the Write channel.
For OS X it looks like Remove | Write events are yielded for directory deletion and just Remove alone for a file deletion. Still it should be translated to Remove before it hits the Dispatch.
The issue is incomming integration tests would need to instantiate and terminate multiple watcher instances via NewWatcher
. As long as inotify
is implemented using single global and single syscall.InotifyInit
this would break those tests.
There is no guarantee that integration tests for error handling would be capable to do full and proper cleanup of the global inotify
instance, and it's obvious what happens next when a test is not idempotent to a Watcher.
Moreover running tests in parallel would not be possible without it.
Removing directories recursively (either by a rm -r
or os.RemoveAll
) yields an event for each single item in the subtree.
Fsnotify behave weirdly, removing "github.com" directory from the Tree
(fixture_test.go), creates the following events:
=== RUN TestIssue16
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153": REMOVE|WRITE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc/appveyor.yml": REMOVE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc/cli/cli.go": REMOVE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc": REMOVE|WRITE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc/cli": REMOVE|WRITE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc/cmd/fakerpc": REMOVE|WRITE
"/var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com/rjeczalik/fakerpc/cmd/fakerpc/main.go": REMOVE
Note the first event is not /var/folders/gz/wkq7fzp96pl6kld9zhg4dd4r0000gn/T/notify707553153/github.com
as one may expect, but actually its parent dir. Should the filter that that anomaly into account? (Actually the fsnotify.Event.Name
is empty for this particular event, the path printed is just $PWD
).
Open question, any ideas how Windows and Linux would handle that?
I'll revisit this after Dispatch is done.
PS You could test that by running go test -run TestIssue16 -v .
(plus add prints where needed).
This functionality was disabled due to the problems with multiple chans which need different watcher behavior:
Example:
// ...
// Watch InCreate events in current working directory.
notify.Watch(".", ch1, notify.InCreate)
// ...
// Watch InDelete events triggered **ONLY on directories** in current working folder.
notify.Watch(".", ch2, notify.InDelete, notify.InOnlydir)
// This comment is TODO
// examle
notify.Watcher("dir", ch, notify.FileActionAdded) // should return error, program hangs instead
DOD
Please compile and run:
https://gist.github.com/ppknap/bf8d9ab54b9dc131db45
func main() {
ch1, ch2 := make(chan<- EventInfo, 1), make(chan<- EventInfo, 1)
Watch("p1", ch1, Create|Delete) // 1
Watch("p1", ch2, Create) // 2
Watch("p2", ch1, Move) // 3
Watch("p2", ch2, Move) // 4
Stop(ch1) // 5
}
Output:
1) Watch dir: p1, events: Create;Delete;, chan: 0xc210046000 --> Sending a new mask Create;Delete;
2) Watch dir: p1, events: Create;, chan: 0xc210046070 --> No need to update mask.
3) Watch dir: p2, events: Move;, chan: 0xc210046000 --> Sending a new mask Move;
4) Watch dir: p2, events: Move;, chan: 0xc210046070 --> No need to update mask.
5a) Stop dir: p1, chan: 0xc210046000 --> Sending a new mask Create; // we don't need to monitor Delete Event
5b) Stop dir: p2, chan: 0xc210046000 --> No need to update mask // Move is needed by chan: 0xc210046070
=== RUN TestWatcherBasic
2014-12-21 14:23 notify.test[6686] (FSEvents.framework) FSEventStreamFlushSync(): failed assertion 'streamRef->appRunLoop != NULL || streamRef->event_source != NULL'
2014-12-21 14:23 notify.test[6686] (FSEvents.framework) FSEventStreamUnscheduleFromRunLoop(): failed assertion 'streamRef != NULL'
2014-12-21 14:23 notify.test[6686] (FSEvents.framework) FSEventStreamStop(): failed assertion 'streamRef != NULL'
--- FAIL: TestWatcherBasic (1.03 seconds)
watcher.go:190: ExpectEvent test has timed out after 1s for notify.Create - github.com/rjeczalik/fs/fs_test.go (id:0)
FAIL
The idea is to have two differently initialised runtimes - one for regular package and one for tests. Obviously runtimes must be initialised exclusively. I've misunderstood how _.go
works, so something else is needed.
An open problem.
// TODO(rjeczalik)
Currently it's not possible to call notify.Watch
on a path that was already watched, but the watch was invalidated by delete of a file / directory, assuming the user did not clean all his watchpoints by calling notify.Stop
.
Listen for Delete events for both watchpoint tree implementations and mark such watchpoint as inactive. On subsequent notify.Watch
on that path reset the watch to its previous state.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.