You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

watcher.go 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package watcher
  4. import (
  5. "context"
  6. "io/fs"
  7. "os"
  8. "code.gitea.io/gitea/modules/log"
  9. "code.gitea.io/gitea/modules/process"
  10. "github.com/fsnotify/fsnotify"
  11. )
  12. // CreateWatcherOpts are options to configure the watcher
  13. type CreateWatcherOpts struct {
  14. // PathsCallback is used to set the required paths to watch
  15. PathsCallback func(func(path, name string, d fs.DirEntry, err error) error) error
  16. // BeforeCallback is called before any files are watched
  17. BeforeCallback func()
  18. // Between Callback is called between after a watched event has occurred
  19. BetweenCallback func()
  20. // AfterCallback is called as this watcher ends
  21. AfterCallback func()
  22. }
  23. // CreateWatcher creates a watcher labelled with the provided description and running with the provided options.
  24. // The created watcher will create a subcontext from the provided ctx and register it with the process manager.
  25. func CreateWatcher(ctx context.Context, desc string, opts *CreateWatcherOpts) {
  26. go run(ctx, desc, opts)
  27. }
  28. func run(ctx context.Context, desc string, opts *CreateWatcherOpts) {
  29. if opts.BeforeCallback != nil {
  30. opts.BeforeCallback()
  31. }
  32. if opts.AfterCallback != nil {
  33. defer opts.AfterCallback()
  34. }
  35. ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Watcher: "+desc, process.SystemProcessType, true)
  36. defer finished()
  37. log.Trace("Watcher loop starting for %s", desc)
  38. defer log.Trace("Watcher loop ended for %s", desc)
  39. watcher, err := fsnotify.NewWatcher()
  40. if err != nil {
  41. log.Error("Unable to create watcher for %s: %v", desc, err)
  42. return
  43. }
  44. if err := opts.PathsCallback(func(path, _ string, d fs.DirEntry, err error) error {
  45. if err != nil && !os.IsNotExist(err) {
  46. return err
  47. }
  48. log.Trace("Watcher: %s watching %q", desc, path)
  49. _ = watcher.Add(path)
  50. return nil
  51. }); err != nil {
  52. log.Error("Unable to create watcher for %s: %v", desc, err)
  53. _ = watcher.Close()
  54. return
  55. }
  56. // Note we don't call the BetweenCallback here
  57. for {
  58. select {
  59. case event, ok := <-watcher.Events:
  60. if !ok {
  61. _ = watcher.Close()
  62. return
  63. }
  64. log.Debug("Watched file for %s had event: %v", desc, event)
  65. case err, ok := <-watcher.Errors:
  66. if !ok {
  67. _ = watcher.Close()
  68. return
  69. }
  70. log.Error("Error whilst watching files for %s: %v", desc, err)
  71. case <-ctx.Done():
  72. _ = watcher.Close()
  73. return
  74. }
  75. // Recreate the watcher - only call the BetweenCallback after the new watcher is set-up
  76. _ = watcher.Close()
  77. watcher, err = fsnotify.NewWatcher()
  78. if err != nil {
  79. log.Error("Unable to create watcher for %s: %v", desc, err)
  80. return
  81. }
  82. if err := opts.PathsCallback(func(path, _ string, _ fs.DirEntry, err error) error {
  83. if err != nil {
  84. return err
  85. }
  86. _ = watcher.Add(path)
  87. return nil
  88. }); err != nil {
  89. log.Error("Unable to create watcher for %s: %v", desc, err)
  90. _ = watcher.Close()
  91. return
  92. }
  93. // Inform our BetweenCallback that there has been an event
  94. if opts.BetweenCallback != nil {
  95. opts.BetweenCallback()
  96. }
  97. }
  98. }