Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/common-go/watch/file.go
2498 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package watch
6
7
import (
8
"context"
9
"crypto/sha256"
10
"encoding/hex"
11
"fmt"
12
"io"
13
"os"
14
"path/filepath"
15
16
"github.com/fsnotify/fsnotify"
17
18
"github.com/gitpod-io/gitpod/common-go/log"
19
)
20
21
type fileWatcher struct {
22
onChange func()
23
24
watcher *fsnotify.Watcher
25
26
hash string
27
}
28
29
func File(ctx context.Context, path string, onChange func()) error {
30
watcher, err := fsnotify.NewWatcher()
31
if err != nil {
32
return fmt.Errorf("unexpected error creating file watcher: %w", err)
33
}
34
35
fw := &fileWatcher{
36
watcher: watcher,
37
onChange: onChange,
38
}
39
40
// initial hash of the file
41
hash, err := hashConfig(path)
42
if err != nil {
43
return fmt.Errorf("cannot get hash of file %v: %w", path, err)
44
}
45
46
// visible files in a volume are symlinks to files in the writer's data directory.
47
// The files are stored in a hidden timestamped directory which is symlinked to by the data directory.
48
// The timestamped directory and data directory symlink are created in the writer's target dir.
49
// https://pkg.go.dev/k8s.io/kubernetes/pkg/volume/util#AtomicWriter
50
watchDir, _ := filepath.Split(path)
51
err = watcher.Add(watchDir)
52
if err != nil {
53
watcher.Close()
54
return fmt.Errorf("unexpected error watching file %v: %w", path, err)
55
}
56
57
log.Infof("starting watch of file %v", path)
58
59
fw.hash = hash
60
61
go func() {
62
defer func() {
63
if err != nil {
64
log.WithError(err).Error("Stopping file watch")
65
} else {
66
log.Info("Stopping file watch")
67
}
68
69
err = watcher.Close()
70
if err != nil {
71
log.WithError(err).Error("Unexpected error closing file watcher")
72
}
73
}()
74
75
for {
76
select {
77
case event, ok := <-watcher.Events:
78
if !ok {
79
return
80
}
81
82
if !eventOpIs(event, fsnotify.Create) && !eventOpIs(event, fsnotify.Remove) {
83
continue
84
}
85
86
currentHash, err := hashConfig(path)
87
if err != nil {
88
log.WithError(err).WithField("event", event.Name).Warn("Cannot check if config has changed")
89
continue
90
}
91
92
// no change
93
if currentHash == fw.hash {
94
continue
95
}
96
97
log.WithField("path", path).Info("reloading file after change")
98
99
fw.hash = currentHash
100
fw.onChange()
101
case err := <-watcher.Errors:
102
log.WithError(err).Error("Unexpected error watching event")
103
case <-ctx.Done():
104
return
105
}
106
}
107
}()
108
109
return nil
110
}
111
112
func hashConfig(path string) (hash string, err error) {
113
f, err := os.Open(path)
114
if err != nil {
115
return "", err
116
}
117
defer f.Close()
118
119
h := sha256.New()
120
121
_, err = io.Copy(h, f)
122
if err != nil {
123
return "", err
124
}
125
126
return hex.EncodeToString(h.Sum(nil)), nil
127
}
128
129
func eventOpIs(event fsnotify.Event, op fsnotify.Op) bool {
130
return event.Op&op == op
131
}
132
133