Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 67 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<!-- fileutils-example-start -->
```go
Expand All @@ -29,6 +29,7 @@ import (
"log"
"os"
"path/filepath"
"time"

"github.com/go-pkgz/fileutils"
"github.com/go-pkgz/fileutils/enum"
Expand All @@ -45,48 +46,101 @@ 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
}

recursiveWatcher, err := fileutils.WatchRecursive(workDir, handleEvent)
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
}
```
Expand Down
76 changes: 65 additions & 11 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"os"
"path/filepath"
"time"

"github.com/go-pkgz/fileutils"
"github.com/go-pkgz/fileutils/enum"
Expand All @@ -21,47 +22,100 @@ 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
}

recursiveWatcher, err := fileutils.WatchRecursive(workDir, handleEvent)
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
}