#!/usr/bin/env php folder = $folder; $this->exclude = $exclude; echo "\nFiles in " . realpath($folder) . ":\n\n"; foreach ($this->files() as $f) { echo "- " . $f->getRealPath() . "\n"; $this->timestamps[$f->getRealPath()] = $f->getMTime(); } $this->watch(); } protected function watch() { do { sleep(1); $this->process(); } while (true); } protected function process() { $seen = []; foreach ($this->files() as $f) { $path = $f->getRealPath(); $seen[$path] = 1; try { $modified = $f->getMTime(); } catch (RuntimeException $e) { $this->deleted($path); continue; } if (!array_key_exists($path, $this->timestamps)) { $this->changed($path); } else { if ($this->timestamps[$path] !== $modified) { $this->changed($path); } } } $deleted = array_diff_key($this->timestamps, $seen); foreach ($deleted as $path=>$file) { $this->deleted($path); } } protected function changed($path) { // make sure to trigger filesystem event echo "\nChanged: $path"; if (abs(filemtime($path) - $this->timestamps[$path]) > 1) { touch($path); } $this->timestamps[$path] = filemtime($path); } protected function deleted($path) { // make sure to trigger filesystem event echo "\nDeleted: $path"; // the original deletion of the file didn't trigger an inotify event // therefore, we re-create the file that was deleted ... touch($path); // ... and give unison chance to catch up ... sleep(5); // ... after which we delete it with a 'native' delete event // that IS picked up by unison unlink($path); // and we forget about the file completely unset($this->timestamps[$path]); } /** * @return SplFileInfo[] A list of files */ protected function files() { $exclude = $this->exclude; $directory = new \RecursiveDirectoryIterator($this->folder, \FilesystemIterator::FOLLOW_SYMLINKS); $filter = new \RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) use ($exclude) { // Skip hidden files and directories. if ($current->getFilename()[0] === '.') { return FALSE; } if ($current->isDir()) { return !in_array($current->getFilename(), $exclude); } // Only recurse into intended subdirectories. return !in_array($current->getRealPath(), $exclude); }); $iterator = new \RecursiveIteratorIterator($filter); foreach ($iterator as $info) { yield $info; } } } if ($argc !== 2) { echo "SYNTAX: ./watcher.php \n"; die; } $folder = $argv[1]; if (!file_exists($folder)) { echo "Folder {$folder} does not exist\n"; die; } new Watcher($argv[1]);