glastree_on_gitea
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Commands;
|
||||
|
||||
use DirectoryTree\ImapEngine\MessageQueryInterface;
|
||||
|
||||
class ConfigureIdleQuery
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $with = []
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Configure the query.
|
||||
*/
|
||||
public function __invoke(MessageQueryInterface $query): MessageQueryInterface
|
||||
{
|
||||
if (in_array('flags', $this->with)) {
|
||||
$query->withFlags();
|
||||
}
|
||||
|
||||
if (in_array('body', $this->with)) {
|
||||
$query->withBody();
|
||||
}
|
||||
|
||||
if (in_array('headers', $this->with)) {
|
||||
$query->withHeaders();
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Commands;
|
||||
|
||||
use Carbon\CarbonInterface;
|
||||
use DirectoryTree\ImapEngine\Laravel\Events\MessageReceived;
|
||||
use DirectoryTree\ImapEngine\MessageInterface;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class HandleMessageReceived
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected WatchMailbox $command,
|
||||
protected int &$attempts = 0,
|
||||
protected ?CarbonInterface &$lastReceivedAt = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handle the message received event.
|
||||
*/
|
||||
public function __invoke(MessageInterface $message): void
|
||||
{
|
||||
$this->command->info(
|
||||
"Message received: [{$message->uid()}]"
|
||||
);
|
||||
|
||||
$this->attempts = 0;
|
||||
|
||||
$this->lastReceivedAt = Date::now();
|
||||
|
||||
Event::dispatch(new MessageReceived($message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Commands;
|
||||
|
||||
use DirectoryTree\ImapEngine\FolderInterface;
|
||||
use DirectoryTree\ImapEngine\Laravel\Events\MailboxWatchAttemptsExceeded;
|
||||
use DirectoryTree\ImapEngine\Laravel\Facades\Imap;
|
||||
use DirectoryTree\ImapEngine\Laravel\Support\LoopInterface;
|
||||
use DirectoryTree\ImapEngine\MailboxInterface;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Exception\InvalidOptionException;
|
||||
|
||||
class WatchMailbox extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'imap:watch {mailbox} {folder?} {--method=idle} {--with=} {--timeout=30} {--attempts=5} {--debug=false}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Watch a mailbox for new messages.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(LoopInterface $loop): void
|
||||
{
|
||||
if (! in_array($method = $this->option('method'), ['idle', 'poll'])) {
|
||||
throw new InvalidOptionException("Invalid method [{$method}]. Valid options are [idle, poll].");
|
||||
}
|
||||
|
||||
$mailbox = Imap::mailbox($name = $this->argument('mailbox'));
|
||||
|
||||
$with = explode(',', $this->option('with'));
|
||||
|
||||
$this->info("Watching mailbox [$name]...");
|
||||
|
||||
$attempts = 0;
|
||||
|
||||
$lastReceivedAt = null;
|
||||
|
||||
$loop->run(function () use ($mailbox, $name, $with, &$attempts, &$lastReceivedAt) {
|
||||
try {
|
||||
$folder = $this->folder($mailbox);
|
||||
|
||||
match ($this->option('method')) {
|
||||
'idle' => $folder->idle(
|
||||
new HandleMessageReceived($this, $attempts, $lastReceivedAt),
|
||||
new ConfigureIdleQuery($with),
|
||||
$this->option('timeout'),
|
||||
),
|
||||
'poll' => $folder->poll(
|
||||
new HandleMessageReceived($this, $attempts, $lastReceivedAt),
|
||||
new ConfigureIdleQuery($with),
|
||||
$this->option('timeout'),
|
||||
),
|
||||
};
|
||||
} catch (Exception $e) {
|
||||
if ($this->isMessageMissing($e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isDisconnection($e)) {
|
||||
sleep(2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($attempts >= $this->option('attempts')) {
|
||||
$this->info("Exception: {$e->getMessage()}");
|
||||
|
||||
Event::dispatch(
|
||||
new MailboxWatchAttemptsExceeded($name, $attempts, $e, $lastReceivedAt)
|
||||
);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$attempts++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mailbox folder to idle.
|
||||
*/
|
||||
protected function folder(MailboxInterface $mailbox): FolderInterface
|
||||
{
|
||||
return ($folder = $this->argument('folder'))
|
||||
? $mailbox->folders()->findOrFail($folder)
|
||||
: $mailbox->inbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the exception is due to a message missing error.
|
||||
*/
|
||||
protected function isMessageMissing(Exception $e): bool
|
||||
{
|
||||
return Str::contains($e->getMessage(), [
|
||||
'no longer exist',
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the exception is caused by a disconnection.
|
||||
*/
|
||||
protected function isDisconnection(Exception $e): bool
|
||||
{
|
||||
return Str::contains($e->getMessage(), [
|
||||
'connection reset by peer',
|
||||
'temporary system problem',
|
||||
'failed to fetch content',
|
||||
'connection failed',
|
||||
'empty response',
|
||||
'not connected',
|
||||
'no response',
|
||||
'broken pipe',
|
||||
'unavailable',
|
||||
], true);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Events;
|
||||
|
||||
use Carbon\CarbonInterface;
|
||||
use Exception;
|
||||
|
||||
class MailboxWatchAttemptsExceeded
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $mailbox,
|
||||
public int $attempts,
|
||||
public Exception $exception,
|
||||
public ?CarbonInterface $lastReceivedAt = null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Events;
|
||||
|
||||
use DirectoryTree\ImapEngine\MessageInterface;
|
||||
|
||||
class MessageReceived
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public MessageInterface $message
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Facades;
|
||||
|
||||
use DirectoryTree\ImapEngine\Laravel\ImapManager;
|
||||
use DirectoryTree\ImapEngine\Testing\FakeMailbox;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* @method static \DirectoryTree\ImapEngine\MailboxInterface mailbox(string $name)
|
||||
* @method static \DirectoryTree\ImapEngine\MailboxInterface swap(string $name, \DirectoryTree\ImapEngine\MailboxInterface $mailbox)
|
||||
*/
|
||||
class Imap extends Facade
|
||||
{
|
||||
/**
|
||||
* Get the registered name of the component.
|
||||
*/
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return ImapManager::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake the given mailbox.
|
||||
*/
|
||||
public static function fake(
|
||||
string $mailbox,
|
||||
array $config = [],
|
||||
array $folders = [],
|
||||
array $capabilities = []
|
||||
): FakeMailbox {
|
||||
/** @var \DirectoryTree\ImapEngine\Laravel\ImapManager $manager */
|
||||
$manager = static::getFacadeRoot();
|
||||
|
||||
$fake = new FakeMailbox($config, $folders, $capabilities);
|
||||
|
||||
$manager->swap($mailbox, $fake);
|
||||
|
||||
return $fake;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel;
|
||||
|
||||
use DirectoryTree\ImapEngine\Mailbox;
|
||||
use DirectoryTree\ImapEngine\MailboxInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ImapManager
|
||||
{
|
||||
/**
|
||||
* The IMAP configuration.
|
||||
*/
|
||||
protected array $config = [];
|
||||
|
||||
/**
|
||||
* The mailbox instances.
|
||||
*/
|
||||
protected array $mailboxes = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mailbox instance.
|
||||
*/
|
||||
public function mailbox(string $name): MailboxInterface
|
||||
{
|
||||
if (isset($this->mailboxes[$name])) {
|
||||
return $this->mailboxes[$name];
|
||||
}
|
||||
|
||||
if (! array_key_exists($name, $this->config['mailboxes'] ?? [])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Mailbox [{$name}] is not defined. Please check your IMAP configuration."
|
||||
);
|
||||
}
|
||||
|
||||
return $this->mailboxes[$name] = $this->build($this->config['mailboxes'][$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mailbox instance.
|
||||
*/
|
||||
public function register(string $name, array $config): static
|
||||
{
|
||||
$this->mailboxes[$name] = $this->build($config);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an on-demand mailbox instance.
|
||||
*/
|
||||
public function build(array $config): MailboxInterface
|
||||
{
|
||||
return new Mailbox($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a mailbox from the in-memory cache.
|
||||
*/
|
||||
public function forget(string $name): static
|
||||
{
|
||||
unset($this->mailboxes[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap out a mailbox instance with a new one.
|
||||
*/
|
||||
public function swap(string $name, MailboxInterface $mailbox): void
|
||||
{
|
||||
$this->mailboxes[$name] = $mailbox;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel;
|
||||
|
||||
use DirectoryTree\ImapEngine\Laravel\Support\Loop;
|
||||
use DirectoryTree\ImapEngine\Laravel\Support\LoopInterface;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ImapServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(ImapManager::class, function () {
|
||||
return new ImapManager(config('imap', []));
|
||||
});
|
||||
|
||||
$this->app->bind(LoopInterface::class, Loop::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
Commands\WatchMailbox::class,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../config/imap.php' => config_path('imap.php'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Support;
|
||||
|
||||
class Loop implements LoopInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(callable $tick): void
|
||||
{
|
||||
while (true) {
|
||||
$tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Support;
|
||||
|
||||
class LoopFake implements LoopInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run(callable $tick): void
|
||||
{
|
||||
$tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DirectoryTree\ImapEngine\Laravel\Support;
|
||||
|
||||
interface LoopInterface
|
||||
{
|
||||
/**
|
||||
* Execute the loop.
|
||||
*/
|
||||
public function run(callable $tick): void;
|
||||
}
|
||||
Reference in New Issue
Block a user