diff --git a/example/main.go b/example/main.go index fc3c332..95ebe3c 100644 --- a/example/main.go +++ b/example/main.go @@ -17,29 +17,41 @@ func main() { if err != nil { log.Fatalf("Failed to create MkdirTemp: %v", err) } + rootDev, err := fsevents.DeviceForPath("/") + if err != nil { + log.Fatalf("Failed to retrieve device for root path: %v", err) + } + dev, err := fsevents.DeviceForPath(path) if err != nil { log.Fatalf("Failed to retrieve device for path: %v", err) } - log.Print(dev) + if dev == rootDev { + dev = 0 + } + log.Println(fsevents.EventIDForDeviceBeforeTime(dev, time.Now())) es := &fsevents.EventStream{ Paths: []string{path}, Latency: 500 * time.Millisecond, Device: dev, - Flags: fsevents.FileEvents | fsevents.WatchRoot} - es.Start() - ec := es.Events + Flags: fsevents.FileEvents | fsevents.WatchRoot | fsevents.NoDefer} + if dev != 0 { + es.Resume = true + es.EventID = fsevents.EventIDForDeviceBeforeTime(dev, time.Now()) + } - log.Println("Device UUID", fsevents.GetDeviceUUID(dev)) + log.Printf("Device UUID %s, device ID: %d, path :%s\n", fsevents.GetDeviceUUID(dev), dev, path) + es.Start() go func() { - for msg := range ec { + for msg := range es.Events { for _, event := range msg { logEvent(event) } } + log.Println("Finished logging events") }() in := bufio.NewReader(os.Stdin) diff --git a/wrap.go b/wrap.go index 84aa6b0..f86e799 100644 --- a/wrap.go +++ b/wrap.go @@ -6,6 +6,11 @@ package fsevents #cgo LDFLAGS: -framework CoreServices #include #include +#include +#include +#include + +int getfsstat(struct statfs *buf, int bufsize, int flags); static CFArrayRef ArrayCreateMutable(int len) { return CFArrayCreateMutable(NULL, len, &kCFTypeArrayCallBacks); @@ -34,6 +39,8 @@ import ( "path/filepath" "reflect" "runtime" + "strings" + "syscall" "time" "unsafe" ) @@ -361,22 +368,72 @@ func EventIDForDeviceBeforeTime(dev int32, before time.Time) uint64 { return uint64(C.FSEventsGetLastEventIdForDeviceBeforeTime(C.dev_t(dev), tm)) } +func charsToString(buf []int8) string { + ret := make([]byte, 0, len(buf)) + for _, c := range buf { + if c == 0 { + continue + } + ret = append(ret, byte(c)) + } + return string(ret) +} + +func getRelativePathsForDevices() (map[int32]string, error) { + num, err := syscall.Getfsstat(nil, C.MNT_NOWAIT) + if err != nil { + return nil, err + } + + buf := make([]syscall.Statfs_t, num) + _, err = syscall.Getfsstat(buf, C.MNT_NOWAIT) + if err != nil { + return nil, err + } + + ret := make(map[int32]string) + for _, fs := range buf[:num] { + ret[fs.Fsid.Val[0]] = charsToString(fs.Mntonname[:]) + } + + return ret, nil +} + // createPaths accepts the user defined set of paths and returns FSEvents // compatible array of paths -func createPaths(paths []string) (C.CFArrayRef, error) { - cPaths := C.ArrayCreateMutable(C.int(len(paths))) - var errs []error - for _, path := range paths { - p, err := filepath.Abs(path) - if err != nil { - // hack up some reporting errors, but don't prevent execution - // because of them - errs = append(errs, err) +func createPaths(paths []string, deviceID int32) (C.CFArrayRef, error) { + var ( + cPaths = C.ArrayCreateMutable(C.int(len(paths))) + relativePaths map[int32]string + errs []error + str C.CFStringRef + p, path string + err error + ) + + if deviceID > 0 { + relativePaths, err = getRelativePathsForDevices() + } + for _, path = range paths { + if devicePath, ok := relativePaths[deviceID]; ok { + // Ensure each path is stripped of it's device's mount path + if strings.HasPrefix(filepath.Clean(path), filepath.Clean(devicePath)) { + path = strings.TrimLeft(filepath.Clean(path), filepath.Clean(devicePath)) + } + str = makeCFString(path) + } else { + // Use absolute path + p, err = filepath.Abs(path) + if err != nil { + // hack up some reporting errors, but don't prevent execution + // because of them + errs = append(errs, err) + } + str = makeCFString(p) } - str := makeCFString(p) C.CFArrayAppendValue(C.CFMutableArrayRef(cPaths), unsafe.Pointer(str)) } - var err error + if len(errs) > 0 { err = fmt.Errorf("%q", errs) } @@ -399,7 +456,9 @@ func cfArrayLen(ref C.CFArrayRef) int { } func setupStream(paths []string, flags CreateFlags, callbackInfo uintptr, eventID uint64, latency time.Duration, deviceID int32) fsEventStreamRef { - cPaths, err := createPaths(paths) + + // Use relative paths for devices since the device path is already present; issue #49 + cPaths, err := createPaths(paths, deviceID) if err != nil { log.Printf("Error creating paths: %s", err) }