Watcher plugin fixes
* com.noccy.watcher: Initial inotify support * ScriptRunner now accepts an array of local vars for expansion when evaluating scripts
This commit is contained in:
		@@ -17,10 +17,12 @@ class FileWatcher {
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        if (extension_loaded('inotify')) {
 | 
			
		||||
            $this->monitor = new MtimeMonitor();
 | 
			
		||||
            //$this->monitor = new InotifyMonitor();
 | 
			
		||||
        if (extension_loaded('inotify') && !getenv("SPARK_NO_INOTIFY")) {
 | 
			
		||||
            //$this->monitor = new MtimeMonitor();
 | 
			
		||||
            printf("Enabling inotify support, watching directories\n");
 | 
			
		||||
            $this->monitor = new InotifyMonitor();
 | 
			
		||||
        } else {
 | 
			
		||||
            printf("No inotify support, watching file mtimes\n");
 | 
			
		||||
            $this->monitor = new MtimeMonitor();
 | 
			
		||||
        }
 | 
			
		||||
        $this->scriptRunner = get_environment()->getScriptRunner();
 | 
			
		||||
@@ -38,7 +40,11 @@ class FileWatcher {
 | 
			
		||||
    private function triggerRule(Rule $rule) 
 | 
			
		||||
    {
 | 
			
		||||
        $actions = $rule->getActions();
 | 
			
		||||
        $this->scriptRunner->evaluate($actions);
 | 
			
		||||
        $locals = [
 | 
			
		||||
            'WATCHER_RULE' => $rule->getName(),
 | 
			
		||||
            'WATCHER_FILES' => join(" ",$rule->getWatchedFiles()),
 | 
			
		||||
        ];
 | 
			
		||||
        $this->scriptRunner->evaluate($actions, $locals);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function loop()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								plugins/com.noccy.watcher/Monitor/InotifyMonitor.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								plugins/com.noccy.watcher/Monitor/InotifyMonitor.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace SparkPlug\Com\Noccy\Watcher\Monitor;
 | 
			
		||||
 | 
			
		||||
use SparkPlug\Com\Noccy\Watcher\Rule;
 | 
			
		||||
 | 
			
		||||
class InotifyMonitor implements MonitorInterface
 | 
			
		||||
{
 | 
			
		||||
    private array $rules = [];
 | 
			
		||||
 | 
			
		||||
    private array $watched = [];
 | 
			
		||||
 | 
			
		||||
    private array $modified = [];
 | 
			
		||||
 | 
			
		||||
    private $fd;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->fd = \inotify_init();   
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __destruct()
 | 
			
		||||
    {
 | 
			
		||||
        if (is_resource($this->fd)) fclose($this->fd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function add(Rule $rule)
 | 
			
		||||
    {
 | 
			
		||||
        $this->rules[] = $rule;
 | 
			
		||||
 | 
			
		||||
        $paths = $rule->getWatchedFiles();
 | 
			
		||||
        $check = [];
 | 
			
		||||
        foreach ($paths as $path) {
 | 
			
		||||
            if (str_contains($path, '*')) {
 | 
			
		||||
                $check = array_merge($check, glob($path));
 | 
			
		||||
            } else {
 | 
			
		||||
                $check[] = $path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $dirs = [];
 | 
			
		||||
        foreach ($check as $path) {
 | 
			
		||||
            $dir = is_dir($path) ? $path : dirname($path);
 | 
			
		||||
            if (!array_key_exists($dir, $dirs)) {
 | 
			
		||||
                $dirs[$dir] = $rule;
 | 
			
		||||
                \inotify_add_watch($this->fd, $dir, \IN_ATTRIB);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $this->watched = array_merge($this->watched, $dirs);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function getModified(): array
 | 
			
		||||
    {
 | 
			
		||||
        $mod = $this->modified;
 | 
			
		||||
        $this->modified = [];
 | 
			
		||||
        return $mod;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function getWatched(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [];        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function loop()
 | 
			
		||||
    {
 | 
			
		||||
        $read = [ $this->fd ];
 | 
			
		||||
        $write = null;
 | 
			
		||||
        $except = null;
 | 
			
		||||
        $changed = [];
 | 
			
		||||
        while (stream_select($read,$write,$except,0)) {
 | 
			
		||||
            $events = \inotify_read($this->fd);
 | 
			
		||||
            foreach ($events as $event) {
 | 
			
		||||
                $changed[] = $event['name'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($changed as $file) {
 | 
			
		||||
            foreach ($this->watched as $dir=>$rule) {
 | 
			
		||||
                if (file_exists($dir."/".$file)) {
 | 
			
		||||
                    if (!in_array($rule, $this->modified)) {
 | 
			
		||||
                        printf("~ modified: %s (%s)\n", $dir."/".$file, $rule->getName());
 | 
			
		||||
                        $this->modified[] = $rule;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -60,12 +60,13 @@ class MtimeMonitor implements MonitorInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($check as $path) {
 | 
			
		||||
            if (!file_exists($path)) continue;
 | 
			
		||||
            if (empty($this->watched[$path])) {
 | 
			
		||||
                $this->watched[$path] = filemtime($path);
 | 
			
		||||
            } else {
 | 
			
		||||
                $mtime = filemtime($path);
 | 
			
		||||
                if ($mtime > $this->watched[$path]) {
 | 
			
		||||
                    printf("* modified: %s (%s)\n", $path, $rule->getName());
 | 
			
		||||
                    printf("~ modified: %s (%s)\n", $path, $rule->getName());
 | 
			
		||||
                    $this->watched[$path] = $mtime;
 | 
			
		||||
                    if (!in_array($rule, $this->modified)) {
 | 
			
		||||
                        $this->modified[] = $rule;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,4 +34,21 @@ issues.
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The `initial-trigger` key controls whether the rule is triggered on startup.
 | 
			
		||||
- `name` contains an optional name of the rule.
 | 
			
		||||
- `initial-trigger` controls whether the rule is triggered on startup. If false
 | 
			
		||||
  or not specified, the actions will be triggered when the file is modified
 | 
			
		||||
  after startup.
 | 
			
		||||
- `watch` is an array of files or wildcards to watch.
 | 
			
		||||
- `actions` are script actions to invoke.
 | 
			
		||||
 | 
			
		||||
The executed action will have access to the `WATCHER_RULE` and `WATCHER_FILES`
 | 
			
		||||
variables for expansion.
 | 
			
		||||
 | 
			
		||||
## Known issues
 | 
			
		||||
 | 
			
		||||
- The *inotify* monitor will trigger whenever a file is changed in a watched
 | 
			
		||||
  directory, even if an explicit file is set. For example, changing the file
 | 
			
		||||
  `./templates/index.html` will trigger the rule for `./templates/style.scss`.
 | 
			
		||||
  If you can't live with that, export `SPARK_NO_INOTIFY=1` before invoking!
 | 
			
		||||
- The *mtime* monitor will poll the mtime of each watched file every check.
 | 
			
		||||
  This works great if you are explicit in what you are watching.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user