Skip to content

Commit b36ac85

Browse files
committed
Add polling support for new messages in folders
1 parent 7ba88a6 commit b36ac85

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

src/Folder.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,33 @@ function (int $msgn) use ($callback, $fetch) {
137137
);
138138
}
139139

140+
/**
141+
* {@inheritDoc}
142+
*/
143+
public function poll(int $frequency, callable $callback, ?callable $query = null): void
144+
{
145+
// The message query to use when fetching messages.
146+
$query ??= fn (MessageQuery $query) => $query;
147+
148+
(new Poll(clone $this->mailbox, $this->path, $frequency))->start(
149+
function (MessageInterface $message) use ($callback, $query) {
150+
if (! $this->mailbox->connected()) {
151+
$this->mailbox->connect();
152+
}
153+
154+
try {
155+
// Apply the query to the message if needed.
156+
$query($this->messages());
157+
158+
$callback($message);
159+
} catch (Exception) {
160+
// Something happened. We will attempt reconnecting.
161+
$this->mailbox->reconnect();
162+
}
163+
}
164+
);
165+
}
166+
140167
/**
141168
* {@inheritDoc}
142169
*/

src/FolderInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public function messages(): MessageQueryInterface;
4646
*/
4747
public function idle(callable $callback, ?callable $query = null, callable|int $timeout = 300): void;
4848

49+
/**
50+
* Poll for new messages at a given frequency.
51+
*/
52+
public function poll(int $frequency, callable $callback, ?callable $query = null): void;
53+
4954
/**
5055
* Move or rename the current folder.
5156
*/

src/Poll.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine;
4+
5+
use Closure;
6+
use DirectoryTree\ImapEngine\Exceptions\Exception;
7+
use DirectoryTree\ImapEngine\Exceptions\ImapConnectionClosedException;
8+
9+
class Poll
10+
{
11+
/**
12+
* The last seen message UID.
13+
*/
14+
protected ?int $lastSeenUid = null;
15+
16+
/**
17+
* Constructor.
18+
*/
19+
public function __construct(
20+
protected Mailbox $mailbox,
21+
protected string $folder,
22+
protected Closure|int $frequency,
23+
) {}
24+
25+
/**
26+
* Destructor.
27+
*/
28+
public function __destruct()
29+
{
30+
$this->disconnect();
31+
}
32+
33+
/**
34+
* Poll for new messages at a given frequency.
35+
*/
36+
public function start(callable $callback): void
37+
{
38+
$this->connect();
39+
40+
while ($frequency = $this->getNextFrequency()) {
41+
try {
42+
$this->check($callback);
43+
} catch (ImapConnectionClosedException) {
44+
$this->reconnect();
45+
}
46+
47+
sleep($frequency);
48+
}
49+
}
50+
51+
/**
52+
* Check for new messages since the last seen UID.
53+
*/
54+
protected function check(callable $callback): void
55+
{
56+
$folder = $this->folder();
57+
58+
$query = $folder->messages();
59+
60+
// If we have a last seen UID, search for messages after it.
61+
if ($this->lastSeenUid !== null) {
62+
$query->uid($this->lastSeenUid + 1, INF);
63+
}
64+
65+
$messages = $query->get();
66+
67+
foreach ($messages as $message) {
68+
$callback($message);
69+
70+
$this->lastSeenUid = $message->uid();
71+
}
72+
}
73+
74+
/**
75+
* Get the folder to poll.
76+
*/
77+
protected function folder(): FolderInterface
78+
{
79+
return $this->mailbox->folders()->findOrFail($this->folder);
80+
}
81+
82+
/**
83+
* Reconnect the client and restart the poll session.
84+
*/
85+
protected function reconnect(): void
86+
{
87+
$this->mailbox->disconnect();
88+
89+
$this->connect();
90+
}
91+
92+
/**
93+
* Connect the client and select the folder to poll.
94+
*/
95+
protected function connect(): void
96+
{
97+
$this->mailbox->connect();
98+
99+
$this->mailbox->select($this->folder(), true);
100+
}
101+
102+
/**
103+
* Disconnect the client.
104+
*/
105+
protected function disconnect(): void
106+
{
107+
try {
108+
$this->mailbox->disconnect();
109+
} catch (Exception) {
110+
// Do nothing.
111+
}
112+
}
113+
114+
/**
115+
* Get the next frequency in seconds.
116+
*/
117+
protected function getNextFrequency(): int|false
118+
{
119+
if (is_numeric($seconds = value($this->frequency))) {
120+
return abs((int) $seconds);
121+
}
122+
123+
return false;
124+
}
125+
}

src/Testing/FakeFolder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ public function idle(callable $callback, ?callable $query = null, callable|int $
9696
}
9797
}
9898

99+
/**
100+
* {@inheritDoc}
101+
*/
102+
public function poll(int $frequency, callable $callback, ?callable $query = null): void
103+
{
104+
foreach ($this->messages as $message) {
105+
$callback($message);
106+
}
107+
}
108+
99109
/**
100110
* {@inheritDoc}
101111
*/

0 commit comments

Comments
 (0)