gitea

Development moved to Codeberg

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

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()
		}
	}
}