2022-08-28 09:43:25 +00:00
|
|
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
2022-11-27 18:20:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-08-28 09:43:25 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|