/
gfsnotify.go
166 lines (149 loc) · 5.34 KB
/
gfsnotify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gfsnotify provides a platform-independent interface for file system notifications.
package gfsnotify
import (
"context"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/errors/gcode"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/gogf/gf/container/glist"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gqueue"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/os/gcache"
)
// Watcher is the monitor for file changes.
type Watcher struct {
watcher *fsnotify.Watcher // Underlying fsnotify object.
events *gqueue.Queue // Used for internal event management.
cache *gcache.Cache // Used for repeated event filter.
nameSet *gset.StrSet // Used for AddOnce feature.
callbacks *gmap.StrAnyMap // Path(file/folder) to callbacks mapping.
closeChan chan struct{} // Used for watcher closing notification.
}
// Callback is the callback function for Watcher.
type Callback struct {
Id int // Unique id for callback object.
Func func(event *Event) // Callback function.
Path string // Bound file path (absolute).
name string // Registered name for AddOnce.
elem *glist.Element // Element in the callbacks of watcher.
recursive bool // Is bound to path recursively or not.
}
// Event is the event produced by underlying fsnotify.
type Event struct {
event fsnotify.Event // Underlying event.
Path string // Absolute file path.
Op Op // File operation.
Watcher *Watcher // Parent watcher.
}
// Op is the bits union for file operations.
type Op uint32
const (
CREATE Op = 1 << iota
WRITE
REMOVE
RENAME
CHMOD
)
const (
repeatEventFilterDuration = time.Millisecond // Duration for repeated event filter.
callbackExitEventPanicStr = "exit" // Custom exit event for internal usage.
)
var (
mu sync.Mutex // Mutex for concurrent safety of defaultWatcher.
defaultWatcher *Watcher // Default watcher.
callbackIdMap = gmap.NewIntAnyMap(true) // Id to callback mapping.
callbackIdGenerator = gtype.NewInt() // Atomic id generator for callback.
)
// New creates and returns a new watcher.
// Note that the watcher number is limited by the file handle setting of the system.
// Eg: fs.inotify.max_user_instances system variable in linux systems.
func New() (*Watcher, error) {
w := &Watcher{
cache: gcache.New(),
events: gqueue.New(),
nameSet: gset.NewStrSet(true),
closeChan: make(chan struct{}),
callbacks: gmap.NewStrAnyMap(true),
}
if watcher, err := fsnotify.NewWatcher(); err == nil {
w.watcher = watcher
} else {
intlog.Printf(context.TODO(), "New watcher failed: %v", err)
return nil, err
}
w.watchLoop()
w.eventLoop()
return w, nil
}
// Add monitors `path` using default watcher with callback function `callbackFunc`.
// The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default.
func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
w, err := getDefaultWatcher()
if err != nil {
return nil, err
}
return w.Add(path, callbackFunc, recursive...)
}
// AddOnce monitors `path` using default watcher with callback function `callbackFunc` only once using unique name `name`.
// If AddOnce is called multiple times with the same `name` parameter, `path` is only added to monitor once. It returns error
// if it's called twice with the same `name`.
//
// The optional parameter `recursive` specifies whether monitoring the `path` recursively, which is true in default.
func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
w, err := getDefaultWatcher()
if err != nil {
return nil, err
}
return w.AddOnce(name, path, callbackFunc, recursive...)
}
// Remove removes all monitoring callbacks of given `path` from watcher recursively.
func Remove(path string) error {
w, err := getDefaultWatcher()
if err != nil {
return err
}
return w.Remove(path)
}
// RemoveCallback removes specified callback with given id from watcher.
func RemoveCallback(callbackId int) error {
w, err := getDefaultWatcher()
if err != nil {
return err
}
callback := (*Callback)(nil)
if r := callbackIdMap.Get(callbackId); r != nil {
callback = r.(*Callback)
}
if callback == nil {
return gerror.NewCodef(gcode.CodeInvalidParameter, `callback for id %d not found`, callbackId)
}
w.RemoveCallback(callbackId)
return nil
}
// Exit is only used in the callback function, which can be used to remove current callback
// of itself from the watcher.
func Exit() {
panic(callbackExitEventPanicStr)
}
// getDefaultWatcher creates and returns the default watcher.
// This is used for lazy initialization purpose.
func getDefaultWatcher() (*Watcher, error) {
mu.Lock()
defer mu.Unlock()
if defaultWatcher != nil {
return defaultWatcher, nil
}
var err error
defaultWatcher, err = New()
return defaultWatcher, err
}