Skip to content

Commit e76aa73

Browse files
committed
Added Scheduler module
1 parent 91c0417 commit e76aa73

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

src/Internal/ProcessTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ trait ProcessTrait
2424
private string|null $group = null;
2525
private int $exitCode = 0;
2626
private LoggerInterface $logger;
27-
private readonly string $socketFile;
27+
private string $socketFile;
2828

2929
public function run(WorkerContext $workerContext): int
3030
{

src/Plugin/Scheduler/Scheduler.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Luzrain\PHPStreamServer\Plugin\Scheduler;
6+
7+
use Amp\Future;
8+
use Luzrain\PHPStreamServer\Internal\MasterProcess;
9+
use Luzrain\PHPStreamServer\PeriodicProcess;
10+
use Luzrain\PHPStreamServer\Plugin\PluginInterface;
11+
use Luzrain\PHPStreamServer\Server;
12+
use function Amp\async;
13+
14+
final readonly class Scheduler implements PluginInterface
15+
{
16+
/**
17+
* @param string|\Closure(PeriodicProcess): void $command bash command as string or php closure
18+
*/
19+
public function __construct(
20+
private string $schedule,
21+
private string|\Closure $command,
22+
private string|null $name = null,
23+
private int $jitter = 0,
24+
private string|null $user = null,
25+
private string|null $group = null,
26+
) {
27+
}
28+
29+
public function start(MasterProcess $masterProcess): void
30+
{
31+
$name = match (true) {
32+
$this->name === null && \is_string($this->command) => $this->command,
33+
$this->name === null => 'closure',
34+
default => $this->name,
35+
};
36+
37+
if (\is_string($this->command) && $this->isCommandContainsLogicOperators($this->command)) {
38+
throw new \RuntimeException(\sprintf(
39+
'%s does not directly support executing multiple commands with logical operators. Use shell with -c option e.g. "/bin/sh -c "%s"',
40+
Server::NAME,
41+
$this->command
42+
));
43+
}
44+
45+
$masterProcess->addWorker(new PeriodicProcess(
46+
name: $name,
47+
schedule: $this->schedule,
48+
jitter: $this->jitter,
49+
user: $this->user,
50+
group: $this->group,
51+
onStart: function (PeriodicProcess $worker) {
52+
if (\is_string($this->command)) {
53+
$worker->exec(...$this->prepareCommand($this->command));
54+
} else {
55+
($this->command)($worker);
56+
}
57+
},
58+
));
59+
}
60+
61+
/**
62+
* @return array{0: string, 1: list<string>}
63+
*/
64+
private function prepareCommand(string $command): array
65+
{
66+
\preg_match_all('/\'[^\']*\'|"[^"]*"|\S+/', $command, $matches);
67+
$parts = \array_map(static fn (string $part) => \trim($part, '"\''), $matches[0]);
68+
$binary = \array_shift($parts);
69+
$args = $parts;
70+
71+
if (!\str_starts_with($binary, '/')) {
72+
if (\is_string($absoluteBinaryPath = \shell_exec("command -v $binary"))) {
73+
$binary = \trim($absoluteBinaryPath);
74+
}
75+
}
76+
77+
return [$binary, $args];
78+
}
79+
80+
private function isCommandContainsLogicOperators(string $command): bool
81+
{
82+
return \preg_match('/(\'[^\']*\'|"[^"]*")(*SKIP)(*FAIL)|&&|\|\|/', $command) === 1;
83+
}
84+
85+
public function stop(): Future
86+
{
87+
return async(static fn() => null);
88+
}
89+
}

0 commit comments

Comments
 (0)