123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- // Copyright 2022 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package watcher
-
- import (
- "context"
- "io/fs"
- "os"
-
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/process"
-
- "github.com/fsnotify/fsnotify"
- )
-
- // CreateWatcherOpts are options to configure the watcher
- type CreateWatcherOpts struct {
- // PathsCallback is used to set the required paths to watch
- PathsCallback func(func(path, name string, d fs.DirEntry, err error) error) error
-
- // BeforeCallback is called before any files are watched
- BeforeCallback func()
-
- // Between Callback is called between after a watched event has occurred
- BetweenCallback func()
-
- // AfterCallback is called as this watcher ends
- AfterCallback func()
- }
-
- // CreateWatcher creates a watcher labelled with the provided description and running with the provided options.
- // The created watcher will create a subcontext from the provided ctx and register it with the process manager.
- func CreateWatcher(ctx context.Context, desc string, opts *CreateWatcherOpts) {
- go run(ctx, desc, opts)
- }
-
- func run(ctx context.Context, desc string, opts *CreateWatcherOpts) {
- if opts.BeforeCallback != nil {
- opts.BeforeCallback()
- }
- if opts.AfterCallback != nil {
- defer opts.AfterCallback()
- }
- ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Watcher: "+desc, process.SystemProcessType, true)
- defer finished()
-
- log.Trace("Watcher loop starting for %s", desc)
- defer log.Trace("Watcher loop ended for %s", desc)
-
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Error("Unable to create watcher for %s: %v", desc, err)
- return
- }
- if err := opts.PathsCallback(func(path, _ string, d fs.DirEntry, err error) error {
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- log.Trace("Watcher: %s watching %q", desc, path)
- _ = watcher.Add(path)
- return nil
- }); err != nil {
- log.Error("Unable to create watcher for %s: %v", desc, err)
- _ = watcher.Close()
- return
- }
-
- // Note we don't call the BetweenCallback here
-
- for {
- select {
- case event, ok := <-watcher.Events:
- if !ok {
- _ = watcher.Close()
- return
- }
- log.Debug("Watched file for %s had event: %v", desc, event)
- case err, ok := <-watcher.Errors:
- if !ok {
- _ = watcher.Close()
- return
- }
- log.Error("Error whilst watching files for %s: %v", desc, err)
- case <-ctx.Done():
- _ = watcher.Close()
- return
- }
-
- // Recreate the watcher - only call the BetweenCallback after the new watcher is set-up
- _ = watcher.Close()
- watcher, err = fsnotify.NewWatcher()
- if err != nil {
- log.Error("Unable to create watcher for %s: %v", desc, err)
- return
- }
- if err := opts.PathsCallback(func(path, _ string, _ fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
- _ = watcher.Add(path)
- return nil
- }); err != nil {
- log.Error("Unable to create watcher for %s: %v", desc, err)
- _ = watcher.Close()
- return
- }
-
- // Inform our BetweenCallback that there has been an event
- if opts.BetweenCallback != nil {
- opts.BetweenCallback()
- }
- }
- }
|