4
4
namespace RTC \Watcher ;
5
5
6
6
7
- use JetBrains \PhpStorm \Pure ;
7
+ use RecursiveDirectoryIterator ;
8
+ use RecursiveIteratorIterator ;
8
9
use RTC \Watcher \Watching \EventInfo ;
9
10
use RTC \Watcher \Watching \EventTrait ;
10
11
use RTC \Watcher \Watching \WatchedItem ;
11
- use Swoole \Event as SWEvent ;
12
+ use Swoole \Event as SwooleEvent ;
12
13
13
14
class Watcher
14
15
{
15
- use EventTrait;
16
+ use EventTrait {
17
+ EventTrait::__construct as private __eventTraitConstructor;
18
+ }
16
19
17
20
protected mixed $ inotifyFD ;
18
21
protected array $ paths = [];
19
22
protected array $ extensions = [];
20
23
protected array $ watchedItems = [];
21
24
25
+ protected readonly int $ maskItemCreated ;
26
+ protected readonly int $ maskItemDeleted ;
27
+
22
28
protected array $ fileShouldNotEndWith = [
23
29
'~ '
24
30
];
25
31
26
32
27
- #[Pure] public static function create (): Watcher
33
+ public static function create (): Watcher
28
34
{
29
35
return new Watcher ();
30
36
}
31
37
38
+ public function __construct ()
39
+ {
40
+ $ this ->__eventTraitConstructor ();
41
+
42
+ $ this ->maskItemCreated = Event::ON_CREATE_HIGH ->value ;
43
+ $ this ->maskItemDeleted = Event::ON_DELETE_HIGH ->value ;
44
+ }
45
+
32
46
/**
33
47
* Get inotify file descriptor
34
48
*
@@ -44,6 +58,8 @@ public function getInotifyFD(): mixed
44
58
}
45
59
46
60
/**
61
+ * Returns list of files currently being watched
62
+ *
47
63
* @return WatchedItem[]
48
64
*/
49
65
public function getWatchedItems (): array
@@ -58,49 +74,143 @@ public function getWatchedItems(): array
58
74
*/
59
75
public function watch (): void
60
76
{
61
- static $ index = 1 ;
62
-
63
77
// Register paths
64
78
foreach ($ this ->paths as $ path ) {
65
- $ this ->watchedItems [$ index ] = $ path ;
66
- inotify_add_watch ($ this ->getInotifyFD (), $ path , $ this ->event ->value );
67
- $ index += 1 ;
79
+ $ this ->recursivelyRegisterInotifyEvent ($ path );
68
80
}
69
81
70
82
// Set up a new event listener for inotify read events
71
- SWEvent ::add ($ this ->getInotifyFD (), function () {
72
- $ events = inotify_read ($ this ->getInotifyFD ());
83
+ SwooleEvent ::add ($ this ->getInotifyFD (), function () {
84
+ $ inotifyEvents = inotify_read ($ this ->getInotifyFD ());
73
85
74
86
// IF WE ARE LISTENING TO 'ON_ALL_EVENTS'
75
87
if ($ this ->willWatchAny ) {
76
- foreach ($ events as $ event ) {
77
- if (! empty ( $ event [ ' name ' ])) { // Filter out invalid events
78
- $ this -> fireEvent ( $ event );
79
- }
88
+ foreach ($ inotifyEvents as $ inotifyEvent ) {
89
+ $ this -> inotifyPerformAdditionalOperations ( $ inotifyEvent );
90
+
91
+ $ this -> fireEvent ( $ inotifyEvent );
80
92
}
81
93
82
94
return ;
83
95
}
84
96
85
97
// INDIVIDUAL LISTENERS
86
- foreach ($ events as $ event ) {
98
+ foreach ($ inotifyEvents as $ inotifyEvent ) {
99
+ $ this ->inotifyPerformAdditionalOperations ($ inotifyEvent );
100
+
87
101
// Make sure that we support this event
88
- if (array_key_exists ( $ event ['mask ' ], self :: $ constants ) ) {
89
- $ this ->fireEvent ($ event );
102
+ if ($ inotifyEvent ['mask ' ] == $ this -> event -> value ) {
103
+ $ this ->fireEvent ($ inotifyEvent );
90
104
}
91
105
}
92
106
93
107
});
94
108
95
109
// Set to monitor and listen for read events for the given $fd
96
- SWEvent ::set (
110
+ SwooleEvent ::set (
97
111
$ this ->getInotifyFD (),
98
112
null ,
99
113
null ,
100
114
SWOOLE_EVENT_READ
101
115
);
102
116
}
103
117
118
+ /**
119
+ * Handles directory creation/deletion on the fly
120
+ *
121
+ * @param array $inotifyEvent
122
+ * @return void
123
+ */
124
+ private function inotifyPerformAdditionalOperations (array $ inotifyEvent ): void
125
+ {
126
+ // Handle directory creation
127
+ if ($ inotifyEvent ['mask ' ] == $ this ->maskItemCreated ) {
128
+ $ eventInfo = new EventInfo ($ inotifyEvent , $ this ->watchedItems [$ inotifyEvent ['wd ' ]]);
129
+ // Register this path also if it's directory
130
+ if ($ eventInfo ->getWatchedItem ()->isDir ()) {
131
+ $ this ->recursivelyRegisterInotifyEvent ($ eventInfo ->getWatchedItem ()->getFullPath ());
132
+ }
133
+
134
+ return ;
135
+ }
136
+
137
+ // Handle directory deletion
138
+ if ($ inotifyEvent ['mask ' ] == $ this ->maskItemDeleted ) {
139
+ $ eventInfo = new EventInfo ($ inotifyEvent , $ this ->watchedItems [$ inotifyEvent ['wd ' ]]);
140
+ // Remove this path also if it's directory
141
+ if ($ eventInfo ->getWatchedItem ()->isDir ()) {
142
+ $ this ->removeInotifyEvent ($ eventInfo );
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Register directory/file to inotify watcher
149
+ * Loops through directory recursively and register all it's subdirectories as well
150
+ *
151
+ * @param string $path
152
+ * @return void
153
+ */
154
+ private function recursivelyRegisterInotifyEvent (string $ path ): void
155
+ {
156
+ if (is_dir ($ path )) {
157
+ $ iterator = new RecursiveDirectoryIterator ($ path );
158
+
159
+ // Loop through files
160
+ foreach (new RecursiveIteratorIterator ($ iterator ) as $ file ) {
161
+ if ($ file ->isDir ()/**&& !in_array($file->getRealPath(), $this->watchedItems)**/ ) {
162
+ $ this ->registerInotifyEvent ($ file ->getRealPath ());
163
+ }
164
+ }
165
+
166
+ return ;
167
+ }
168
+
169
+ // Register file watch
170
+ $ this ->registerInotifyEvent ($ path );
171
+ }
172
+
173
+ /**
174
+ * Register directory/file to inotify watcher
175
+ *
176
+ * @param string $path
177
+ * @return void
178
+ */
179
+ private function registerInotifyEvent (string $ path ): void
180
+ {
181
+ $ descriptor = inotify_add_watch (
182
+ $ this ->getInotifyFD (),
183
+ $ path ,
184
+ Event::ON_ALL_EVENTS ->value
185
+ );
186
+
187
+ $ this ->watchedItems [$ descriptor ] = [
188
+ 'path ' => $ path ,
189
+ 'mask ' => $ this ->event ->value ,
190
+ ];
191
+ }
192
+
193
+ /**
194
+ * Stop watching file/directory
195
+ *
196
+ * @param EventInfo $eventInfo
197
+ * @return void
198
+ */
199
+ public function removeInotifyEvent (EventInfo $ eventInfo )
200
+ {
201
+ // Stop watching event
202
+ inotify_rm_watch ($ this ->getInotifyFD (), $ eventInfo ->getWatchDescriptor ());
203
+
204
+ // Stop tracking descriptor
205
+ unset($ this ->watchedItems [$ eventInfo ->getWatchDescriptor ()]);
206
+ }
207
+
208
+ /**
209
+ * Trigger an event
210
+ *
211
+ * @param array $inotifyEvent
212
+ * @return void
213
+ */
104
214
private function fireEvent (array $ inotifyEvent ): void
105
215
{
106
216
$ shouldFireEvent = array_key_exists ($ inotifyEvent ['mask ' ], self ::$ constants );
@@ -131,7 +241,7 @@ private function fireEvent(array $inotifyEvent): void
131
241
132
242
$ eventMask = $ this ->willWatchAny
133
243
? Event::ON_ALL_EVENTS ->value
134
- : $ eventInfo ->getMask ();
244
+ : $ eventInfo ->getMask ()-> value ;
135
245
136
246
$ this ->eventEmitter ->emit ($ eventMask , [$ eventInfo ]);
137
247
}
0 commit comments