diff --git a/README.md b/README.md index 201e22d..24e9782 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Package `fileutils` provides useful, high-level file operations. ## Complete example -The following program creates and copies a file, calculates its SHA-256 checksum, and configures both watcher variants. It is also tracked as [`examples/basic/main.go`](examples/basic/main.go). +The following program copies and moves a file, checks what exists, calculates MD5 and SHA-256 checksums, reserves a temporary name, and then runs both watcher variants until a change is reported. It is also tracked as [`examples/basic/main.go`](examples/basic/main.go). ```go @@ -29,6 +29,7 @@ import ( "log" "os" "path/filepath" + "time" "github.com/go-pkgz/fileutils" "github.com/go-pkgz/fileutils/enum" @@ -45,38 +46,77 @@ func run() error { if err != nil { return err } - defer os.RemoveAll(workDir) + defer func() { _ = os.RemoveAll(workDir) }() source := filepath.Join(workDir, "source.txt") if writeErr := os.WriteFile(source, []byte("fileutils example\n"), 0o600); writeErr != nil { return writeErr } - destination := filepath.Join(workDir, "copied.txt") - if copyErr := fileutils.CopyFile(source, destination); copyErr != nil { + // copy the file, then move the copy into a directory that does not exist yet + copied := filepath.Join(workDir, "copied.txt") + if copyErr := fileutils.CopyFile(source, copied); copyErr != nil { return copyErr } + moved := filepath.Join(workDir, "archive", "moved.txt") + if moveErr := fileutils.MoveFile(copied, moved); moveErr != nil { + return moveErr + } + fmt.Printf("IsFile(moved.txt): %v\n", fileutils.IsFile(moved)) + fmt.Printf("IsDir(archive): %v\n", fileutils.IsDir(filepath.Dir(moved))) + + // any algorithm from the enum package works the same way + md5sum, err := fileutils.Checksum(moved, enum.HashAlgMD5) + if err != nil { + return err + } + sha256sum, err := fileutils.Checksum(moved, enum.HashAlgSHA256) + if err != nil { + return err + } + fmt.Printf("MD5: %s\n", md5sum) + fmt.Printf("SHA-256: %s\n", sha256sum) - checksum, err := fileutils.Checksum(destination, enum.HashAlgSHA256) + // a free name to write to, the file itself is not created + tempName, err := fileutils.TempFileName(workDir, "upload-*.tmp") if err != nil { return err } - fmt.Printf("SHA-256: %s\n", checksum) + fmt.Printf("temp name: %s\n", filepath.Base(tempName)) + return watch(workDir, source) +} + +// watch starts both watcher variants, makes a change and waits for it to be reported. +func watch(workDir, source string) error { + // the callback runs on the watcher goroutine, so hand the event over instead of working here + events := make(chan fileutils.FileEvent, 16) handleEvent := func(event fileutils.FileEvent) { - fmt.Printf("Event: %s, path: %s\n", event.Type, event.Path) + select { + case events <- event: + default: // the buffer is enough for this example, drop the rest + } } fileWatcher, err := fileutils.NewFileWatcher(source, handleEvent) if err != nil { return err } - defer fileWatcher.Close() - - if addErr := fileWatcher.AddPath(destination); addErr != nil { + defer func() { + if closeErr := fileWatcher.Close(); closeErr != nil { + log.Printf("close file watcher: %v", closeErr) + } + }() + + // paths can be added and removed while the watcher runs + extra := filepath.Join(workDir, "extra.txt") + if touchErr := fileutils.TouchFile(extra); touchErr != nil { + return touchErr + } + if addErr := fileWatcher.AddPath(extra); addErr != nil { return addErr } - if removeErr := fileWatcher.RemovePath(destination); removeErr != nil { + if removeErr := fileWatcher.RemovePath(extra); removeErr != nil { return removeErr } @@ -84,9 +124,23 @@ func run() error { if err != nil { return err } - defer recursiveWatcher.Close() + defer func() { + if closeErr := recursiveWatcher.Close(); closeErr != nil { + log.Printf("close recursive watcher: %v", closeErr) + } + }() + + // the paths are registered before the constructors return, so this change is already watched + if changeErr := os.WriteFile(source, []byte("changed\n"), 0o600); changeErr != nil { + return changeErr + } - fmt.Printf("Watching %s\n", workDir) + select { + case event := <-events: + fmt.Printf("Event: %s, path: %s\n", event.Type, event.Path) + case <-time.After(5 * time.Second): + fmt.Println("no event within the timeout") + } return nil } ``` diff --git a/examples/basic/main.go b/examples/basic/main.go index 6f8f163..ad00f91 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -5,6 +5,7 @@ import ( "log" "os" "path/filepath" + "time" "github.com/go-pkgz/fileutils" "github.com/go-pkgz/fileutils/enum" @@ -21,38 +22,77 @@ func run() error { if err != nil { return err } - defer os.RemoveAll(workDir) + defer func() { _ = os.RemoveAll(workDir) }() source := filepath.Join(workDir, "source.txt") if writeErr := os.WriteFile(source, []byte("fileutils example\n"), 0o600); writeErr != nil { return writeErr } - destination := filepath.Join(workDir, "copied.txt") - if copyErr := fileutils.CopyFile(source, destination); copyErr != nil { + // copy the file, then move the copy into a directory that does not exist yet + copied := filepath.Join(workDir, "copied.txt") + if copyErr := fileutils.CopyFile(source, copied); copyErr != nil { return copyErr } + moved := filepath.Join(workDir, "archive", "moved.txt") + if moveErr := fileutils.MoveFile(copied, moved); moveErr != nil { + return moveErr + } + fmt.Printf("IsFile(moved.txt): %v\n", fileutils.IsFile(moved)) + fmt.Printf("IsDir(archive): %v\n", fileutils.IsDir(filepath.Dir(moved))) + + // any algorithm from the enum package works the same way + md5sum, err := fileutils.Checksum(moved, enum.HashAlgMD5) + if err != nil { + return err + } + sha256sum, err := fileutils.Checksum(moved, enum.HashAlgSHA256) + if err != nil { + return err + } + fmt.Printf("MD5: %s\n", md5sum) + fmt.Printf("SHA-256: %s\n", sha256sum) - checksum, err := fileutils.Checksum(destination, enum.HashAlgSHA256) + // a free name to write to, the file itself is not created + tempName, err := fileutils.TempFileName(workDir, "upload-*.tmp") if err != nil { return err } - fmt.Printf("SHA-256: %s\n", checksum) + fmt.Printf("temp name: %s\n", filepath.Base(tempName)) + return watch(workDir, source) +} + +// watch starts both watcher variants, makes a change and waits for it to be reported. +func watch(workDir, source string) error { + // the callback runs on the watcher goroutine, so hand the event over instead of working here + events := make(chan fileutils.FileEvent, 16) handleEvent := func(event fileutils.FileEvent) { - fmt.Printf("Event: %s, path: %s\n", event.Type, event.Path) + select { + case events <- event: + default: // the buffer is enough for this example, drop the rest + } } fileWatcher, err := fileutils.NewFileWatcher(source, handleEvent) if err != nil { return err } - defer fileWatcher.Close() + defer func() { + if closeErr := fileWatcher.Close(); closeErr != nil { + log.Printf("close file watcher: %v", closeErr) + } + }() - if addErr := fileWatcher.AddPath(destination); addErr != nil { + // paths can be added and removed while the watcher runs + extra := filepath.Join(workDir, "extra.txt") + if touchErr := fileutils.TouchFile(extra); touchErr != nil { + return touchErr + } + if addErr := fileWatcher.AddPath(extra); addErr != nil { return addErr } - if removeErr := fileWatcher.RemovePath(destination); removeErr != nil { + if removeErr := fileWatcher.RemovePath(extra); removeErr != nil { return removeErr } @@ -60,8 +100,22 @@ func run() error { if err != nil { return err } - defer recursiveWatcher.Close() + defer func() { + if closeErr := recursiveWatcher.Close(); closeErr != nil { + log.Printf("close recursive watcher: %v", closeErr) + } + }() - fmt.Printf("Watching %s\n", workDir) + // the paths are registered before the constructors return, so this change is already watched + if changeErr := os.WriteFile(source, []byte("changed\n"), 0o600); changeErr != nil { + return changeErr + } + + select { + case event := <-events: + fmt.Printf("Event: %s, path: %s\n", event.Type, event.Path) + case <-time.After(5 * time.Second): + fmt.Println("no event within the timeout") + } return nil }