diff --git a/lib/fs/basicfs_watch_eventtypes_darwin.go b/lib/fs/basicfs_watch_eventtypes_darwin.go new file mode 100644 index 000000000..c7fae5d14 --- /dev/null +++ b/lib/fs/basicfs_watch_eventtypes_darwin.go @@ -0,0 +1,17 @@ +// Copyright (C) 2021 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build darwin,!kqueue,cgo + +package fs + +import "github.com/syncthing/notify" + +const ( + subEventMask = notify.Create | notify.Remove | notify.Write | notify.Rename | notify.FSEventsInodeMetaMod + permEventMask = 0 + rmEventMask = notify.Remove | notify.Rename +) diff --git a/lib/fs/basicfs_watch_eventtypes_other.go b/lib/fs/basicfs_watch_eventtypes_other.go index 85df0ab9f..7e6c23df3 100644 --- a/lib/fs/basicfs_watch_eventtypes_other.go +++ b/lib/fs/basicfs_watch_eventtypes_other.go @@ -4,8 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. -// +build !linux,!windows,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris -// +build !darwin darwin,cgo +// +build !linux,!windows,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!darwin,!cgo // Catch all platforms that are not specifically handled to use the generic // event types. diff --git a/lib/fs/basicfs_watch_test.go b/lib/fs/basicfs_watch_test.go index ccf5e928a..9be15dc84 100644 --- a/lib/fs/basicfs_watch_test.go +++ b/lib/fs/basicfs_watch_test.go @@ -12,6 +12,7 @@ import ( "context" "errors" "fmt" + "io/ioutil" "os" "path/filepath" "runtime" @@ -445,6 +446,61 @@ func TestWatchModTime(t *testing.T) { } } + testScenario(t, name, testCase, expectedEvents, allowedEvents, fakeMatcher{}, false) +} + +func TestModifyFile(t *testing.T) { + name := "modify" + + old := createTestFile(name, "file") + modifyTestFile(name, old, "syncthing") + + testCase := func() { + modifyTestFile(name, old, "modified") + } + + expectedEvents := []Event{ + {old, NonRemove}, + } + allowedEvents := []Event{ + {name, NonRemove}, + } + + sleepMs(1000) + testScenario(t, name, testCase, expectedEvents, allowedEvents, fakeMatcher{}, false) +} + +func TestTruncateFileOnly(t *testing.T) { + name := "truncate" + + file := createTestFile(name, "file") + modifyTestFile(name, file, "syncthing") + + // modified the content to empty use os.WriteFile will first truncate the file + // (/os/file.go:696) then write nothing. This logic is also used in many editors, + // such as when emptying a file in VSCode or JetBrain + // + // darwin will only modified the inode's metadata, such us mtime, file size, etc. + // but would not modified the file directly, so FSEvent 'FSEventsModified' will not + // be received + // + // we should watch the FSEvent 'FSEventsInodeMetaMod' to watch the Inode metadata, + // and that should be considered as an NonRemove Event + // + // notify also considered FSEventsInodeMetaMod as Write Event + // /watcher_fsevents.go:89 + testCase := func() { + modifyTestFile(name, file, "") + } + + expectedEvents := []Event{ + {file, NonRemove}, + } + allowedEvents := []Event{ + {file, NonRemove}, + } + + sleepMs(1000) testScenario(t, name, testCase, expectedEvents, allowedEvents, fakeMatcher{}, true) } @@ -470,6 +526,15 @@ func renameTestFile(name string, old string, new string) { } } +func modifyTestFile(name string, file string, content string) { + joined := filepath.Join(testDirAbs, name, file) + + err := ioutil.WriteFile(joined, []byte(content), 0755) + if err != nil { + panic(fmt.Sprintf("Failed to modify test file %s: %s", joined, err)) + } +} + func sleepMs(ms int) { time.Sleep(time.Duration(ms) * time.Millisecond) }