diff --git a/src/Folder.php b/src/Folder.php index bd30cc2..1b72e66 100644 --- a/src/Folder.php +++ b/src/Folder.php @@ -2,6 +2,7 @@ namespace DirectoryTree\ImapEngine; +use Closure; use DirectoryTree\ImapEngine\Connection\ImapQueryBuilder; use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse; use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier; @@ -92,12 +93,17 @@ public function messages(): MessageQuery /** * {@inheritDoc} */ - public function idle(callable $callback, ?callable $query = null, int $timeout = 300): void + public function idle(callable $callback, ?callable $query = null, callable|int $timeout = 300): void { if (! in_array('IDLE', $this->mailbox->capabilities())) { throw new ImapCapabilityException('Unable to IDLE. IMAP server does not support IDLE capability.'); } + // Normalize timeout into a closure. + if (is_callable($timeout) && ! $timeout instanceof Closure) { + $timeout = $timeout(...); + } + // The message query to use when fetching messages. $query ??= fn (MessageQuery $query) => $query; diff --git a/src/FolderInterface.php b/src/FolderInterface.php index d6a5910..e0d1102 100644 --- a/src/FolderInterface.php +++ b/src/FolderInterface.php @@ -44,7 +44,7 @@ public function messages(): MessageQueryInterface; /** * Begin idling on the current folder. */ - public function idle(callable $callback, ?callable $query = null, int $timeout = 300): void; + public function idle(callable $callback, ?callable $query = null, callable|int $timeout = 300): void; /** * Move or rename the current folder. diff --git a/src/Idle.php b/src/Idle.php index ed45659..60f8a9c 100644 --- a/src/Idle.php +++ b/src/Idle.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Carbon\CarbonInterface; +use Closure; use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse; use DirectoryTree\ImapEngine\Exceptions\Exception; use DirectoryTree\ImapEngine\Exceptions\ImapConnectionClosedException; @@ -18,7 +19,7 @@ class Idle public function __construct( protected Mailbox $mailbox, protected string $folder, - protected int $timeout, + protected Closure|int $timeout, ) {} /** @@ -36,10 +37,7 @@ public function await(callable $callback): void { $this->connect(); - // Loop indefinitely, restarting IDLE sessions as needed. - while (true) { - $ttl = $this->getNextTimeout(); - + while ($ttl = $this->getNextTimeout()) { try { $this->listen($callback, $ttl); } catch (ImapConnectionTimedOutException) { @@ -56,7 +54,7 @@ public function await(callable $callback): void protected function listen(callable $callback, CarbonInterface $ttl): void { // Iterate over responses yielded by the idle generator. - foreach ($this->idle() as $response) { + foreach ($this->idle($ttl) as $response) { if (! $response instanceof UntaggedResponse) { continue; } @@ -69,6 +67,10 @@ protected function listen(callable $callback, CarbonInterface $ttl): void $ttl = $this->getNextTimeout(); } + if ($ttl === false) { + break; + } + // If we've been idle too long, break out to restart the session. if (Carbon::now()->greaterThanOrEqualTo($ttl)) { $this->restart(); @@ -145,16 +147,22 @@ protected function done(): void /** * Begin a new IDLE session as a generator. */ - protected function idle(): Generator + protected function idle(CarbonInterface $ttl): Generator { - yield from $this->mailbox->connection()->idle($this->timeout); + yield from $this->mailbox->connection()->idle( + (int) Carbon::now()->diffInSeconds($ttl, true) + ); } /** * Get the next timeout as a Carbon instance. */ - protected function getNextTimeout(): CarbonInterface + protected function getNextTimeout(): CarbonInterface|false { - return Carbon::now()->addSeconds($this->timeout); + if (is_numeric($seconds = value($this->timeout))) { + return Carbon::now()->addSeconds(abs($seconds)); + } + + return false; } } diff --git a/src/Testing/FakeFolder.php b/src/Testing/FakeFolder.php index aafb8a8..d2c6116 100644 --- a/src/Testing/FakeFolder.php +++ b/src/Testing/FakeFolder.php @@ -89,7 +89,7 @@ public function messages(): MessageQueryInterface /** * {@inheritDoc} */ - public function idle(callable $callback, ?callable $query = null, int $timeout = 300): void + public function idle(callable $callback, ?callable $query = null, callable|int $timeout = 300): void { foreach ($this->messages as $message) { $callback($message);