diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 51e5cc805d2ec89eb2e64fc6ce2e16dc4db5ce82..b9e2f2915f45cb125f4776e73aa46b32663e1ddb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ test:package: - apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev libzip-dev zip - apt-get clean - pecl install mcrypt inotify - - docker-php-ext-enable mcrypt + - docker-php-ext-enable mcrypt inotify - docker-php-ext-install zip - curl --silent --show-error "https://getcomposer.org/installer" | php -- --install-dir=/usr/local/bin --filename=composer script: diff --git a/src/Console/WatchCommand.php b/src/Console/WatchCommand.php index a313cbb3b8ae518879595f7c0181fe29db7d6865..73674a673306eb62fac45577ca936504d4b6aa24 100644 --- a/src/Console/WatchCommand.php +++ b/src/Console/WatchCommand.php @@ -18,6 +18,7 @@ class WatchCommand extends Command */ protected $signature = 'watch:run {--force : Force the worker to run even in maintenance mode} + {--once : Specify if the watcher should stop after one file has been detected} {--sleep=3 : Number of seconds to sleep when no job is available} {--rest=0 : Number of seconds to rest between jobs} {--timeout=0 : Only process the event on the queue}'; @@ -99,7 +100,8 @@ class WatchCommand extends Command $this->option('sleep'), $this->option('force'), $this->option('rest'), - $this->option('timeout') + $this->option('timeout'), + $this->option('once') ); } @@ -107,6 +109,7 @@ class WatchCommand extends Command * Listen for the queue events in order to update the console output. * * @return void + * @psalm-suppress UndefinedInterfaceMethod */ protected function listenForEvents(): void { diff --git a/src/Watcher.php b/src/Watcher.php index 0dae1609ea533fee50ab0a2d3ec6ed7893240466..dc7bc176dae122b607551664bf2337072fd56e46 100644 --- a/src/Watcher.php +++ b/src/Watcher.php @@ -48,7 +48,7 @@ class Watcher /** * The inotify instance * - * @var resource + * @var resource|closed-resource */ protected $inotify; @@ -94,9 +94,9 @@ class Watcher * Execute the console command. * * @param WatcherOptions $options - * @return int + * @return int|null */ - public function daemon(WatcherOptions $options): int + public function daemon(WatcherOptions $options) { if ($this->supportsAsyncSignals()) { $this->listenForSignals(); @@ -138,6 +138,10 @@ class Watcher $cookie ); + if ($options->once) { + $this->shouldQuit = true; + } + if ($options->rest > 0) { $this->sleep($options->rest); } @@ -270,11 +274,11 @@ class Watcher protected function stopWatchers(): void { foreach (array_keys($this->watchers) as $watcher) { - inotify_rm_watch($this->inotify, $watcher); + if(inotify_rm_watch($this->inotify, $watcher)) { + unset($this->watchers[$watcher]); + } } - $this->watchers = []; - $this->unregisterWatchers(); } diff --git a/src/WatcherOptions.php b/src/WatcherOptions.php index 68e000076bf056336f5fc75d1087bfe389e3e7fa..f3a8717e6630ea807d496402d6bc4f6d24a27681 100644 --- a/src/WatcherOptions.php +++ b/src/WatcherOptions.php @@ -26,12 +26,19 @@ class WatcherOptions public $force; /** - * Indicates if the watcher should stop after one event. + * Indicates if the watcher should stop after a specific elapsed time (in seconds). * * @var mixed */ public $timeout; + /** + * Indicates if the watcher should stop after one event. + * + * @var mixed + */ + public $once; + /** * Create a new watcher options instance. * @@ -40,11 +47,12 @@ class WatcherOptions * @param mixed $rest * @return void */ - public function __construct($sleep = 5, $force = false, $rest = 0, $timeout = 0) + public function __construct($sleep = 5, $force = false, $rest = 0, $timeout = 0, $once = false) { $this->sleep = $sleep; $this->rest = $rest; $this->force = $force; $this->timeout = $timeout; + $this->once = $once; } } diff --git a/tests/Feature/WatcherCommandTest.php b/tests/Feature/WatcherCommandTest.php index cdd01878699624d59fcb6a87245cff4a7ebaabda..6ece1ed2eac67eba9e62d7d9bc8c94e01462c1ab 100644 --- a/tests/Feature/WatcherCommandTest.php +++ b/tests/Feature/WatcherCommandTest.php @@ -4,7 +4,6 @@ namespace Dterumal\Watcher\Tests\Feature; use Dterumal\Watcher\Events\FileEvent; use Dterumal\Watcher\Events\WatcherCreated; -use Dterumal\Watcher\Events\WatcherRestarted; use Dterumal\Watcher\Events\WatcherStopped; use Dterumal\Watcher\Tests\TestCase; use Illuminate\Support\Facades\Artisan; @@ -73,11 +72,53 @@ class WatcherCommandTest extends TestCase // Check that the watcher was created Event::assertDispatched(FileEvent::class, function ($event) { - ray($event); return $event->directory === 'default' && $event->mask === 256 && $event->cookie === 0 && $event->name === 'test.txt'; }); } + + /** @test */ + function it_detects_a_file_and_exits_with_option_once() + { + Event::fake(); + + // File created + $fooFile = storage_path('app/test.txt'); + + // make sure we're starting from a clean state + if (File::exists($fooFile)) { + unlink($fooFile); + } + + // Start background process + $process = new Process(['/bin/bash', 'sleep-and-create-a-file.sh', $fooFile], $this->getStubDirectory()); + $process->start(); + + // Run the artisan command + $exitCode = Artisan::call('watch:run', [ + '--once' => true + ], new NullOutput); + + // Assert a new file is created + $this->assertTrue(File::exists($fooFile)); + unlink($fooFile); + + // Check that exit code + $this->assertSame(0, $exitCode); + + // Check that the watcher was created + Event::assertDispatched(FileEvent::class, function ($event) { + return $event->directory === 'default' && + $event->mask === 256 && + $event->cookie === 0 && + $event->name === 'test.txt'; + }); + + // Check that the watcher was stopped + Event::assertDispatched(WatcherStopped::class, function ($event) { + return $event->status === 0; + }); + } } \ No newline at end of file