偶尔下载一些视频媒体的时候会想要对它进行压缩来缩小占用空间, 但是如果每次等下载完再去用软件压缩会比较麻烦 这里就找到了inotify扩展,可以监听文件变更 这里其实想吹一下小米的mimo2.5模型,因为要装php扩展, 我的是php7.4,网上搜了下有基于pecl,有的是基于源码 我就直接丢给配了小米模型的hermes agent来安装,直接就给我装好了 顺带再帮我写了个artisan命令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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 <?php namespace App \Console \Commands ;use Illuminate \Console \Command ;class InotifyWatchCommand extends Command { protected $signature = 'file:watch {path : Directory to watch} {--recursive : Watch subdirectories recursively} {--event=* : Events to watch (create, modify, delete, move). Default: create,modify} {--ext=* : Only watch files with these extensions (e.g., --ext=mp4 --ext=mkv)} {--output= : Output file path for logging (optional)} {--format=text : Output format: text or json}' ; protected $description = 'Watch directory for file write events using inotify' ; protected $eventMap = [ 'create' => IN_CREATE, 'modify' => IN_MODIFY, 'delete' => IN_DELETE, 'move' => IN_MOVED_TO, 'close_write' => IN_CLOSE_WRITE, 'open' => IN_OPEN, 'access' => IN_ACCESS, 'attrib' => IN_ATTRIB, ]; protected $inotify ; protected $watchDescriptors = []; protected $outputHandle ; protected $running = true ; public function handle ( ) { $path = trim ($this ->argument ('path' )); $recursive = $this ->option ('recursive' ); $events = $this ->option ('event' ) ?: ['create' , 'modify' ]; $extensions = $this ->option ('ext' ); $outputFile = $this ->option ('output' ); $format = $this ->option ('format' ); if (!is_dir ($path )) { $this ->error ("Directory does not exist: {$path} " ); return 1 ; } if ($outputFile ) { $this ->outputHandle = fopen ($outputFile , 'a' ); if (!$this ->outputHandle) { $this ->error ("Cannot open output file: {$outputFile} " ); return 1 ; } } $eventMask = 0 ; foreach ($events as $event ) { if (!isset ($this ->eventMap[$event ])) { $this ->error ("Unknown event: {$event} . Available: " . implode (', ' , array_keys ($this ->eventMap))); return 1 ; } $eventMask |= $this ->eventMap[$event ]; } $eventMask |= IN_CLOSE_WRITE; $this ->inotify = inotify_init (); if (!$this ->inotify) { $this ->error ("Failed to initialize inotify" ); return 1 ; } stream_set_blocking ($this ->inotify, false ); if (function_exists ('pcntl_signal' )) { pcntl_signal (SIGTERM, [$this , 'handleSignal' ]); pcntl_signal (SIGINT, [$this , 'handleSignal' ]); } $this ->addWatch ($path , $recursive ); $this ->info ("Watching: {$path} " ); $this ->info ("Events: " . implode (', ' , $events )); $this ->info ("Recursive: " . ($recursive ? 'yes' : 'no' )); if ($extensions ) { $this ->info ("Extensions: " . implode (', ' , $extensions )); } $this ->info ("Press Ctrl+C to stop" ); $this ->line ('' ); $checkInterval = 500000 ; while ($this ->running) { if (function_exists ('pcntl_signal_dispatch' )) { pcntl_signal_dispatch (); } $events = inotify_read ($this ->inotify); if ($events !== false && !empty ($events )) { foreach ($events as $event ) { $this ->handleEvent ($event , $extensions , $format ); } } usleep ($checkInterval ); } $this ->cleanup (); return 0 ; } protected function addWatch (string $path , bool $recursive ): void { $mask = IN_CREATE | IN_MODIFY | IN_DELETE | IN_MOVED_TO | IN_CLOSE_WRITE | IN_OPEN | IN_ATTRIB; $wd = inotify_add_watch ($this ->inotify, $path , $mask ); $this ->watchDescriptors[$wd ] = $path ; if ($recursive ) { $items = scandir ($path ); foreach ($items as $item ) { if ($item === '.' || $item === '..' ) { continue ; } $fullPath = $path . DIRECTORY_SEPARATOR . $item ; if (is_dir ($fullPath )) { $this ->addWatch ($fullPath , true ); } } } } protected function handleEvent (array $event , array $extensions , string $format ): void { $wd = $event ['wd' ]; $mask = $event ['mask' ]; $name = $event ['name' ] ?? '' ; $cookie = $event ['cookie' ] ?? 0 ; $dir = $this ->watchDescriptors[$wd ] ?? 'unknown' ; if (empty ($name )) { return ; } if (!empty ($extensions )) { $ext = strtolower (pathinfo ($name , PATHINFO_EXTENSION)); if (!in_array ($ext , $extensions )) { return ; } } $eventType = $this ->getEventType ($mask ); $filePath = $dir . DIRECTORY_SEPARATOR . $name ; $data = [ 'timestamp' => date ('Y-m-d H:i:s' ), 'event' => $eventType , 'file' => $name , 'path' => $filePath , 'dir' => $dir , 'cookie' => $cookie , ]; if ($format === 'json' ) { $output = json_encode ($data , JSON_UNESCAPED_UNICODE) . "\n" ; } else { $output = $this ->formatTextOutput ($data ); } $this ->writeToConsole ($eventType , $output ); if ($this ->outputHandle) { fwrite ($this ->outputHandle, $output ); } } protected function getEventType (int $mask ): string { $types = []; if ($mask & IN_CREATE) { $types [] = 'CREATE' ; } if ($mask & IN_MODIFY) { $types [] = 'MODIFY' ; } if ($mask & IN_DELETE) { $types [] = 'DELETE' ; } if ($mask & IN_MOVED_TO) { $types [] = 'MOVED_TO' ; } if ($mask & IN_CLOSE_WRITE) { $types [] = 'CLOSE_WRITE' ; } if ($mask & IN_OPEN) { $types [] = 'OPEN' ; } if ($mask & IN_ACCESS) { $types [] = 'ACCESS' ; } if ($mask & IN_ATTRIB) { $types [] = 'ATTRIB' ; } return implode ('|' , $types ); } protected function formatTextOutput (array $data ): string { return sprintf ( "[%s] %-12s %s (%s)\n" , $data ['timestamp' ], $data ['event' ], $data ['file' ], $data ['dir' ] ); } protected function writeToConsole (string $eventType , string $output ): void { if (strpos ($eventType , 'CREATE' ) !== false ) { $this ->info (rtrim ($output )); } elseif (strpos ($eventType , 'DELETE' ) !== false ) { $this ->error (rtrim ($output )); } elseif (strpos ($eventType , 'MODIFY' ) !== false || strpos ($eventType , 'CLOSE_WRITE' ) !== false ) { $this ->comment (rtrim ($output )); } elseif (strpos ($eventType , 'MOVED_TO' ) !== false ) { $this ->warn (rtrim ($output )); } else { $this ->line (rtrim ($output )); } } public function handleSignal (int $signal ): void { $this ->running = false ; $this ->line ('' ); $this ->info ("Received signal {$signal} , shutting down..." ); } protected function cleanup ( ): void { if ($this ->outputHandle) { fclose ($this ->outputHandle); } foreach ($this ->watchDescriptors as $wd => $path ) { inotify_rm_watch ($this ->inotify, $wd ); } if (is_resource ($this ->inotify)) { fclose ($this ->inotify); } $this ->info ("Watch stopped." ); } }
只需要执行命令php artisan file:watch /home/nick/listen --recursive 然后我新建一个文本文件
就能看到文件的变更 这个流程下来我都没有任何调整,都是一步到位了 这个因为没有准确的条件对比,可能是hermes也起了一定作用,还是说本身模型就很强 主要是监听了文件的创建,修改,关闭写入等, 首先是inotify_init函数 — 初始化 inotify 实例,类似于客户端 然后是inotify_add_watch 函数 — 添加监听到已初始化的 inotify 实例1 2 3 4 5 6 7 8 inotify_add_watch(resource $inotify_instance, string $pathname, int $mask): int|false 参数 ¶ inotify_instance inotify_init()返回的资源 pathname 要监听的文件或目录 mask 监听事件。详情见 预定义常量。
具体的预定义常量有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 IN_ACCESS (int) 文件被访问(读)(*) IN_MODIFY (int) 文件被修改(*) IN_ATTRIB (int) 元数据变更(例如:权限、修改时间等)(*) IN_CLOSE_WRITE (int) 打开写入后关闭的文件(*) IN_CLOSE_NOWRITE (int) 未打开写入的文件被关闭(*) IN_OPEN (int) 文件被打开(*) IN_MOVED_TO (int) 文件被移动到监听目录(*) IN_MOVED_FROM (int) 文件移出监听目录(*) IN_CREATE (int) 在监听目录中创建文件或文件夹(*) IN_DELETE (int) 监听目录中删除文件或文件夹(*) IN_DELETE_SELF (int) 监听的文件或目录被删除 IN_MOVE_SELF (int) IN_CLOSE (int) 与 IN_CLOSE_WRITE | IN_CLOSE_NOWRITE 相等 IN_MOVE (int) 与 IN_MOVED_FROM | IN_MOVED_TO 相等 IN_ALL_EVENTS (int) 以上所有常量的位掩码 IN_UNMOUNT (int) 卸载包含监听对象的文件系统 IN_Q_OVERFLOW (int) 事件队列溢出(此事件中监听描述符是 -1) IN_IGNORED (int) 监听被移除(通过 inotify_rm_watch() 显示移除,或者是文件被移动,或者文件系统被卸载) IN_ISDIR (int) 事件发生的主体是目录 IN_ONLYDIR (int) 如果是目录,仅监听路径名(从 Linux 2.6.15 开始) IN_DONT_FOLLOW (int) 如果是符号链接,不引用路径名(从 Linux 2.6.15 开始) IN_MASK_ADD (int) 如果路径名存在,追加监听此路径的事件掩码(代替更换掩码) IN_ONESHOT (int) 路径触发一个监听事件后,从监听列表中移除。
接下去是1 inotify_read(resource $inotify_instance): array
会返回以下内容1 2 3 4 wd 是由 inotify_add_watch() 返回的监听描述符 mask 是 events 的位掩码 cookie 是连接相关事件(例如:IN_MOVE_FROM 和 IN_MOVE_TO)的唯一 id name 是文件名(例如:监听目录中被修改的文件)
注意这个返回值1 返回 inotify 事件数组。在没有待处理事件或 inotify_instance 非阻塞时返回 false。事件发生时都会返回包含以下键的数组:
如果要设置非阻塞的话1 stream_set_blocking($ fd, 0);
$fd就是init返回的inotify对象 如果是非阻塞就是自己循环去read有没有事件, 如果是阻塞的话就是read会一直等到有事件了才返回 还可以通过1 inotify_queue_len(resource $inotify_instance): int
如果有数量代表read不是阻塞的,有待处理的事件
最后还有通过inotify_rm_watch来清除监听器1 inotify_rm_watch(resource $inotify_instance, int $watch_descriptor): bool
这样针对文件系统有变更的话就会比较方便去做下一步处理, 比如压缩,移动归档等 之前最开始的处理方式是每次都遍历,感觉还是挺不科学的, 已有的文件会一遍遍被重复扫描 这个方法还有个局限性就是得在Linux系统里, 其他系统例如mac需要fs_usage这些工具才行 Windows里需要使用 ReadDirectoryChangesW 等api 总结下来这个方式对于Linux系统下的ext4系统是比较方便的, 其他的系统可能需要再研究下