Skip to content

Instantly share code, notes, and snippets.

@infostreams
Created April 6, 2020 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save infostreams/0bf20b217bb813eaa21e17ee22df9c1f to your computer and use it in GitHub Desktop.
Save infostreams/0bf20b217bb813eaa21e17ee22df9c1f to your computer and use it in GitHub Desktop.
#!/usr/bin/env php
<?php
/**
* This script monitors a folder for changes and forces a change in the inode or file-modification time
* if it detects a change or deletion.
*
* Basically it's a PHP re-implementation of https://github.com/sillypog/inotify-proxy
*
* I do this because docker-sync was not copying changes from my MacOS folder to volume that was used by
* my docker container. And docker-sync is the only way to get a FAST filesystem if you're using Docker
* on Mac.
*
* Unfortunately, Virtual Box does not trigger inotify events within the container when a file is modified
* on the OSX host. This issue is described here: https://www.virtualbox.org/ticket/10660
* https://www.virtualbox.org/ticket/14234 and marked as WONT-FIX.
*
* Also see https://github.com/EugenMayer/docker-sync/issues/410
*
* @author E. Akerboom
* @date 2020
*/
class Watcher
{
protected $folder;
protected $timestamps;
protected $exclude = [];
public function __construct($folder, $exclude = ["node_modules", "vendor", "storage", "resources", "public"])
{
$this->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 <folder>\n";
die;
}
$folder = $argv[1];
if (!file_exists($folder)) {
echo "Folder {$folder} does not exist\n";
die;
}
new Watcher($argv[1]);
@infostreams
Copy link
Author

infostreams commented Apr 9, 2020

If you want to make the native_osx synching strategy work on docker-machine, you can do that by adding the native_osx_image option to the options section of your docker-sync.yaml:

version: "2"
options:
  native_osx_image: 'infostreams/docker-sync-unison'

syncs:
  code_sync:
    sync_strategy: 'native_osx'
    src: '/my/path/to/my/code'
    sync_excludes: ['node_modules', 'vendor']
    sync_args: "-copyonconflict"

Whatever is listed in sync_excludes is configuration for unison. The script that watches for file changes does not actually use the configuration here. Instead, the script excludes the following folders by default: ["node_modules", "vendor", "storage", "resources", "public"] - and there's no way to change that. If you find a way to change it and make it listen to whatever is in sync_excludes, fix it in the PHP script above and I will update the Docker image on Docker hub.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment