diff --git a/CHANGELOG.md b/CHANGELOG.md index ff432ef..e31b6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ ## [Unreleased] +### Added + +- `Innmind\OperatingSystem\Config::map()` +- `Innmind\OperatingSystem\Config\Logger` +- `Innmind\OperatingSystem\Config\Resilient` +- `Innmind\OperatingSystem\Config::useHttpTransport()` +- `Innmind\OperatingSystem\Config::mapHttpTransport()` +- `Innmind\OperatingSystem\Config::openSQLConnectionVia()` +- `Innmind\OperatingSystem\Config::mapSQLConnection()` +- `Innmind\OperatingSystem\Config::mapServerControl()` +- `Innmind\OperatingSystem\Config::mapServerStatus()` +- `Innmind\OperatingSystem\Config::mapClock()` +- `Innmind\OperatingSystem\Config::mapFileWatch()` +- `Innmind\OperatingSystem\Config::mountFilesystemVia()` +- `Innmind\OperatingSystem\Config::mapFilesystem()` + ### Changed - Requires `innmind/time-continuum:^4.1.1` @@ -23,6 +39,15 @@ - `Innmind\OperatingSystem\Sockets::open()` now returns an `Innmind\Immutable\Attempt` - `Innmind\OperatingSystem\Sockets::takeOver()` now returns an `Innmind\Immutable\Attempt` - `Innmind\OperatingSystem\Sockets::connectTo()` now returns an `Innmind\Immutable\Attempt` +- `Innmind\OperatingSystem\OperatingSystem` is now a final class +- `Innmind\OperatingSystem\Sockets` is now a final class +- `Innmind\OperatingSystem\Remote` is now a final class +- `Innmind\OperatingSystem\Ports` is now a final class +- `Innmind\OperatingSystem\Filesystem` is now a final class +- `Innmind\OperatingSystem\CurrentProcess\Signals` is now a final class +- `Innmind\OperatingSystem\CurrentProcess` is now a final class +- `Innmind\OperatingSystem\OperatingSystem::map()` callable must now return a `Config` +- `Innmind\OperatingSystem\Config::of()` has been renamed `::new()` ### Fixed @@ -32,6 +57,23 @@ - `Innmind\OperatingSystem\Config::useStreamCapabilities()` - `Innmind\OperatingSystem\Sockets::watch()` +- `Innmind\OperatingSystem\OperatingSystem\Resilient` +- `Innmind\OperatingSystem\OperatingSystem\Logger` +- `Innmind\OperatingSystem\Config::limitHttpConcurrencyTo()` use `::useHttpTransport()` instead +- `Innmind\OperatingSystem\Config::withHttpHeartbeat()` use `::useHttpTransport()` instead +- `Innmind\OperatingSystem\Config::disableSSLVerification()` use `::useHttpTransport()` instead +- `Innmind\OperatingSystem\Config::caseInsensitiveFilesystem()` use `::mountFilesystemVia()` instead +- The following informations are no longer logged: + - the current process id + - the current process memory + - the signals listener being added/removed + - the signals received by the current process + - temporary files being created + - opened ports + - opened remote sockets + - opened sockets + - if a file/directory exists or not + - when a PHP file is loaded in memory ## 5.2.0 - 2024-07-14 diff --git a/src/Config.php b/src/Config.php index 286b725..d8b279d 100644 --- a/src/Config.php +++ b/src/Config.php @@ -3,75 +3,103 @@ namespace Innmind\OperatingSystem; -use Innmind\TimeContinuum\{ - Clock, - Period, +use Innmind\Server\Control; +use Innmind\Server\Status; +use Innmind\TimeContinuum\Clock; +use Innmind\HttpTransport\{ + Transport as HttpTransport, + Curl, }; -use Innmind\Filesystem\CaseSensitivity; +use Innmind\Filesystem\{ + Adapter as Filesystem, + CaseSensitivity +}; +use Innmind\FileWatch\Watch; use Innmind\Server\Status\EnvironmentPath; use Innmind\TimeWarp\Halt; use Innmind\IO\IO; -use Innmind\Immutable\Maybe; +use Innmind\Url\{ + Url, + Path, +}; +use Innmind\Immutable\Attempt; +use Formal\AccessLayer; final class Config { - private Clock $clock; - private CaseSensitivity $caseSensitivity; - private IO $io; - private Halt $halt; - private EnvironmentPath $path; - /** @var Maybe */ - private Maybe $maxHttpConcurrency; - /** @var Maybe */ - private Maybe $httpHeartbeat; - private bool $disableSSLVerification; - /** - * @param Maybe $maxHttpConcurrency - * @param Maybe $httpHeartbeat + * @param \Closure(Clock, self): Clock $mapClock + * @param \Closure(Halt, self): Halt $mapHalt + * @param \Closure(HttpTransport, self): HttpTransport $mapHttpTransport + * @param \Closure(Url): AccessLayer\Connection $sql + * @param \Closure(AccessLayer\Connection, self): AccessLayer\Connection $mapSql + * @param \Closure(Control\Server, self): Control\Server $mapServerControl + * @param \Closure(Status\Server, self): Status\Server $mapServerStatus + * @param \Closure(Watch, self): Watch $mapFileWatch + * @param \Closure(Path, self): Attempt $filesystem + * @param \Closure(Filesystem, self): Filesystem $mapFilesystem */ private function __construct( - Clock $clock, - CaseSensitivity $caseSensitivity, - IO $io, - Halt $halt, - EnvironmentPath $path, - Maybe $maxHttpConcurrency, - Maybe $httpHeartbeat, - bool $disableSSLVerification, + private Clock $clock, + private \Closure $mapClock, + private IO $io, + private Halt $halt, + private \Closure $mapHalt, + private EnvironmentPath $path, + private ?HttpTransport $httpTransport, + private \Closure $mapHttpTransport, + private \Closure $sql, + private \Closure $mapSql, + private \Closure $mapServerControl, + private \Closure $mapServerStatus, + private \Closure $mapFileWatch, + private \Closure $filesystem, + private \Closure $mapFilesystem, ) { - $this->clock = $clock; - $this->caseSensitivity = $caseSensitivity; - $this->io = $io; - $this->halt = $halt; - $this->path = $path; - $this->maxHttpConcurrency = $maxHttpConcurrency; - $this->httpHeartbeat = $httpHeartbeat; - $this->disableSSLVerification = $disableSSLVerification; } - public static function of(): self + public static function new(): self { - /** @var Maybe */ - $maxHttpConcurrency = Maybe::nothing(); - /** @var Maybe */ - $httpHeartbeat = Maybe::nothing(); - return new self( Clock::live(), - CaseSensitivity::sensitive, + static fn(Clock $clock) => $clock, IO::fromAmbientAuthority(), Halt\Usleep::new(), + static fn(Halt $halt, self $config) => $halt, EnvironmentPath::of(match ($path = \getenv('PATH')) { false => '', default => $path, }), - $maxHttpConcurrency, - $httpHeartbeat, - false, + null, + static fn(HttpTransport $transport, self $config) => $transport, + static fn(Url $server) => AccessLayer\Connection\Lazy::of( + static fn() => AccessLayer\Connection\PDO::of($server), + ), + static fn(AccessLayer\Connection $connection, self $config) => $connection, + static fn(Control\Server $server, self $config) => $server, + static fn(Status\Server $server, self $config) => $server, + static fn(Watch $watch, self $config) => $watch, + static fn(Path $path, self $config) => Attempt::of( + static fn() => Filesystem\Filesystem::mount( + $path, + $config->io(), + )->withCaseSensitivity(CaseSensitivity::sensitive), + ), + static fn(Filesystem $filesystem, self $config) => $filesystem, ); } + /** + * @psalm-mutation-free + * + * @param callable(self): self $map + */ + public function map(callable $map): self + { + /** @psalm-suppress ImpureFunctionCall */ + return $map($this); + } + /** * @psalm-mutation-free */ @@ -79,30 +107,51 @@ public function withClock(Clock $clock): self { return new self( $clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $this->halt, + $this->mapHalt, $this->path, - $this->maxHttpConcurrency, - $this->httpHeartbeat, - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, ); } /** * @psalm-mutation-free + * + * @param \Closure(Clock, self): Clock $map */ - public function caseInsensitiveFilesystem(): self + public function mapClock(\Closure $map): self { + $previous = $this->mapClock; + return new self( $this->clock, - CaseSensitivity::insensitive, + static fn(Clock $clock, self $config) => $map( + $previous($clock, $config), + $config, + ), $this->io, $this->halt, + $this->mapHalt, $this->path, - $this->maxHttpConcurrency, - $this->httpHeartbeat, - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, ); } @@ -113,13 +162,52 @@ public function haltProcessVia(Halt $halt): self { return new self( $this->clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $halt, + $this->mapHalt, $this->path, - $this->maxHttpConcurrency, - $this->httpHeartbeat, - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(Halt, self): Halt $map + */ + public function mapHalt(\Closure $map): self + { + $previous = $this->mapHalt; + + /** @psalm-suppress ImpureFunctionCall */ + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + static fn(Halt $halt, self $config) => $map( + $previous($halt, $config), + $config, + ), + $this->path, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, ); } @@ -130,68 +218,282 @@ public function withEnvironmentPath(EnvironmentPath $path): self { return new self( $this->clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $this->halt, + $this->mapHalt, $path, - $this->maxHttpConcurrency, - $this->httpHeartbeat, - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + */ + public function useHttpTransport(HttpTransport $transport): self + { + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $transport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(HttpTransport, self): HttpTransport $map + */ + public function mapHttpTransport(\Closure $map): self + { + $previous = $this->mapHttpTransport; + + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $this->httpTransport, + static fn(HttpTransport $transport, self $config) => $map( + $previous($transport, $config), + $config, + ), + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(Url): AccessLayer\Connection $sql + */ + public function openSQLConnectionVia(\Closure $sql): self + { + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $this->httpTransport, + $this->mapHttpTransport, + $sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, ); } /** * @psalm-mutation-free * - * @param positive-int $max + * @param \Closure(AccessLayer\Connection, self): AccessLayer\Connection $map */ - public function limitHttpConcurrencyTo(int $max): self + public function mapSQLConnection(\Closure $map): self { + $previous = $this->mapSql; + + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + static fn(AccessLayer\Connection $connection, self $config) => $map( + $previous($connection, $config), + $config, + ), + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(Control\Server, self): Control\Server $map + */ + public function mapServerControl(\Closure $map): self + { + $previous = $this->mapServerControl; + + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + static fn(Control\Server $server, self $config) => $map( + $previous($server, $config), + $config, + ), + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(Status\Server, self): Status\Server $map + */ + public function mapServerStatus(\Closure $map): self + { + $previous = $this->mapServerStatus; + + return new self( + $this->clock, + $this->mapClock, + $this->io, + $this->halt, + $this->mapHalt, + $this->path, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + static fn(Status\Server $server, self $config) => $map( + $previous($server, $config), + $config, + ), + $this->mapFileWatch, + $this->filesystem, + $this->mapFilesystem, + ); + } + + /** + * @psalm-mutation-free + * + * @param \Closure(Watch, self): Watch $map + */ + public function mapFileWatch(\Closure $map): self + { + $previous = $this->mapFileWatch; + return new self( $this->clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $this->halt, + $this->mapHalt, $this->path, - Maybe::just($max), - $this->httpHeartbeat, - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + static fn(Watch $watch, self $config) => $map( + $previous($watch, $config), + $config, + ), + $this->filesystem, + $this->mapFilesystem, ); } /** * @psalm-mutation-free * - * @param callable(): void $heartbeat + * @param \Closure(Path, self): Attempt $filesystem */ - public function withHttpHeartbeat(Period $timeout, callable $heartbeat): self + public function mountFilesystemVia(\Closure $filesystem): self { return new self( $this->clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $this->halt, + $this->mapHalt, $this->path, - $this->maxHttpConcurrency, - Maybe::just([$timeout, $heartbeat]), - $this->disableSSLVerification, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $filesystem, + $this->mapFilesystem, ); } /** * @psalm-mutation-free + * + * @param \Closure(Filesystem, self): Filesystem $map */ - public function disableSSLVerification(): self + public function mapFilesystem(\Closure $map): self { + $previous = $this->mapFilesystem; + return new self( $this->clock, - $this->caseSensitivity, + $this->mapClock, $this->io, $this->halt, + $this->mapHalt, $this->path, - $this->maxHttpConcurrency, - $this->httpHeartbeat, - true, + $this->httpTransport, + $this->mapHttpTransport, + $this->sql, + $this->mapSql, + $this->mapServerControl, + $this->mapServerStatus, + $this->mapFileWatch, + $this->filesystem, + static fn(Filesystem $filesystem, self $config) => $map( + $previous($filesystem, $config), + $config, + ), ); } @@ -200,15 +502,19 @@ public function disableSSLVerification(): self */ public function clock(): Clock { - return $this->clock; + return ($this->mapClock)($this->clock, $this); } /** * @internal + * + * @return Attempt */ - public function filesystemCaseSensitivity(): CaseSensitivity + public function filesystem(Path $path): Attempt { - return $this->caseSensitivity; + return ($this->filesystem)($path, $this)->map( + fn($adapter) => ($this->mapFilesystem)($adapter, $this), + ); } /** @@ -224,7 +530,7 @@ public function io(): IO */ public function halt(): Halt { - return $this->halt; + return ($this->mapHalt)($this->halt, $this); } /** @@ -237,29 +543,49 @@ public function environmentPath(): EnvironmentPath /** * @internal - * - * @return Maybe */ - public function maxHttpConcurrency(): Maybe + public function httpTransport(): HttpTransport { - return $this->maxHttpConcurrency; + $transport = $this->httpTransport ?? Curl::of( + $this->clock, + $this->io, + ); + + return ($this->mapHttpTransport)($transport, $this); + } + + /** + * @internal + */ + public function sql(Url $url): AccessLayer\Connection + { + return ($this->mapSql)( + ($this->sql)($url), + $this, + ); + } + + /** + * @internal + */ + public function serverControl(Control\Server $server): Control\Server + { + return ($this->mapServerControl)($server, $this); } /** * @internal - * - * @return Maybe */ - public function httpHeartbeat(): Maybe + public function serverStatus(Status\Server $server): Status\Server { - return $this->httpHeartbeat; + return ($this->mapServerStatus)($server, $this); } /** * @internal */ - public function mustDisableSSLVerification(): bool + public function fileWatch(Watch $watch): Watch { - return $this->disableSSLVerification; + return ($this->mapFileWatch)($watch, $this); } } diff --git a/src/Config/Logger.php b/src/Config/Logger.php new file mode 100644 index 0000000..56dcdc2 --- /dev/null +++ b/src/Config/Logger.php @@ -0,0 +1,62 @@ +mapHalt(fn($halt) => Halt\Logger::psr( + $halt, + $this->logger, + )) + ->mapHttpTransport(fn($transport) => HttpTransport\Logger::psr( + $transport, + $this->logger, + )) + ->mapSQLConnection(fn($connection) => Connection\Logger::psr( + $connection, + $this->logger, + )) + ->mapServerControl(fn($server) => Control\Servers\Logger::psr( + $server, + $this->logger, + )) + ->mapServerStatus(fn($server) => Status\Servers\Logger::of( + $server, + $this->logger, + )) + ->mapClock(fn($clock) => Clock::logger($clock, $this->logger)) + ->mapFileWatch(fn($watch) => Watch::logger( + $watch, + $this->logger, + )) + ->mapFilesystem(fn($filesystem) => Filesystem\Logger::psr( + $filesystem, + $this->logger, + )); + } + + public static function psr(LoggerInterface $logger): self + { + return new self($logger); + } +} diff --git a/src/Config/Resilient.php b/src/Config/Resilient.php new file mode 100644 index 0000000..dd04ba6 --- /dev/null +++ b/src/Config/Resilient.php @@ -0,0 +1,29 @@ +mapHttpTransport(static fn($transport, $config) => ExponentialBackoff::of( + $transport, + $config->halt(), + )); + } + + /** + * This config helps retry certain _safe_ operations on remote systems + */ + public static function new(): self + { + return self::instance; + } +} diff --git a/src/CurrentProcess.php b/src/CurrentProcess.php index c1d0eb1..fa2d194 100644 --- a/src/CurrentProcess.php +++ b/src/CurrentProcess.php @@ -7,22 +7,60 @@ use Innmind\Server\Control\Server\Process\Pid; use Innmind\Server\Status\Server\Memory\Bytes; use Innmind\TimeContinuum\Period; +use Innmind\TimeWarp\Halt; use Innmind\Immutable\{ Attempt, SideEffect, }; -interface CurrentProcess +final class CurrentProcess { + private Halt $halt; + private ?Signals $signals = null; + + private function __construct(Halt $halt) + { + $this->halt = $halt; + } + + /** + * @internal + */ + public static function of(Halt $halt): self + { + return new self($halt); + } + /** * @return Attempt */ - public function id(): Attempt; - public function signals(): Signals; + public function id(): Attempt + { + $pid = \getmypid(); + + /** @psalm-suppress ArgumentTypeCoercion */ + return match ($pid) { + false => Attempt::error(new \RuntimeException('Failed to retrieve process id')), + default => Attempt::result(new Pid($pid)), + }; + } + + public function signals(): Signals + { + return $this->signals ??= Signals::of(); + } /** * @return Attempt */ - public function halt(Period $period): Attempt; - public function memory(): Bytes; + public function halt(Period $period): Attempt + { + return ($this->halt)($period); + } + + public function memory(): Bytes + { + /** @psalm-suppress ArgumentTypeCoercion */ + return Bytes::of(\memory_get_usage()); + } } diff --git a/src/CurrentProcess/Generic.php b/src/CurrentProcess/Generic.php deleted file mode 100644 index 71ea177..0000000 --- a/src/CurrentProcess/Generic.php +++ /dev/null @@ -1,62 +0,0 @@ -halt = $halt; - } - - /** - * @internal - */ - public static function of(Halt $halt): self - { - return new self($halt); - } - - #[\Override] - public function id(): Attempt - { - $pid = \getmypid(); - - /** @psalm-suppress ArgumentTypeCoercion */ - return match ($pid) { - false => Attempt::error(new \RuntimeException('Failed to retrieve process id')), - default => Attempt::result(new Pid($pid)), - }; - } - - #[\Override] - public function signals(): Signals - { - return $this->signals ??= Signals\Wrapper::of(new Handler); - } - - #[\Override] - public function halt(Period $period): Attempt - { - return ($this->halt)($period); - } - - #[\Override] - public function memory(): Bytes - { - /** @psalm-suppress ArgumentTypeCoercion */ - return Bytes::of(\memory_get_usage()); - } -} diff --git a/src/CurrentProcess/Logger.php b/src/CurrentProcess/Logger.php deleted file mode 100644 index 918d9a5..0000000 --- a/src/CurrentProcess/Logger.php +++ /dev/null @@ -1,79 +0,0 @@ -process = $process; - $this->logger = $logger; - } - - public static function psr(CurrentProcess $process, LoggerInterface $logger): self - { - return new self($process, $logger); - } - - #[\Override] - public function id(): Attempt - { - return $this->process->id()->map(function($pid) { - $this->logger->debug( - 'Current process id is {pid}', - ['pid' => $pid->toInt()], - ); - - return $pid; - }); - } - - #[\Override] - public function signals(): Signals - { - return $this->signals ??= Signals\Logger::psr( - $this->process->signals(), - $this->logger, - ); - } - - #[\Override] - public function halt(Period $period): Attempt - { - $this->logger->debug('Halting current process...', ['period' => [ - 'years' => $period->years(), - 'months' => $period->months(), - 'days' => $period->days(), - 'hours' => $period->hours(), - 'minutes' => $period->minutes(), - 'seconds' => $period->seconds(), - 'milliseconds' => $period->milliseconds(), - ]]); - - return $this->process->halt($period); - } - - #[\Override] - public function memory(): Bytes - { - $memory = $this->process->memory(); - - $this->logger->debug( - 'Current process memory at {memory}', - ['memory' => $memory->toString()], - ); - - return $memory; - } -} diff --git a/src/CurrentProcess/Signals.php b/src/CurrentProcess/Signals.php index b5dcfac..a22d7d9 100644 --- a/src/CurrentProcess/Signals.php +++ b/src/CurrentProcess/Signals.php @@ -4,19 +4,41 @@ namespace Innmind\OperatingSystem\CurrentProcess; use Innmind\Signals\{ + Handler, Signal, Info, }; -interface Signals +final class Signals { + private Handler $handler; + + private function __construct(Handler $handler) + { + $this->handler = $handler; + } + + /** + * @internal + */ + public static function of(): self + { + return new self(new Handler); + } + /** * @param callable(Signal, Info): void $listener */ - public function listen(Signal $signal, callable $listener): void; + public function listen(Signal $signal, callable $listener): void + { + $this->handler->listen($signal, $listener); + } /** * @param callable(Signal, Info): void $listener */ - public function remove(callable $listener): void; + public function remove(callable $listener): void + { + $this->handler->remove($listener); + } } diff --git a/src/CurrentProcess/Signals/Logger.php b/src/CurrentProcess/Signals/Logger.php deleted file mode 100644 index 375ced1..0000000 --- a/src/CurrentProcess/Signals/Logger.php +++ /dev/null @@ -1,72 +0,0 @@ - */ - private Map $decorated; - - private function __construct(Signals $signals, LoggerInterface $logger) - { - $this->signals = $signals; - $this->logger = $logger; - /** @var Map */ - $this->decorated = Map::of(); - } - - public static function psr(Signals $signals, LoggerInterface $logger): self - { - return new self($signals, $logger); - } - - #[\Override] - public function listen(Signal $signal, callable $listener): void - { - $this->logger->debug( - 'Registering a listener for signal {signal}', - ['signal' => $signal->toInt()], - ); - - $decorated = function(Signal $signal, Info $info) use ($listener): void { - $this->logger->debug( - 'Handling signal {signal}', - ['signal' => $signal->toInt()], - ); - - $listener($signal, $info); - }; - $this->decorated = ($this->decorated)($listener, $decorated); - - $this->signals->listen($signal, $decorated); - } - - #[\Override] - public function remove(callable $listener): void - { - $this->logger->debug('Removing a signal listener'); - - // by default we alias the user listener as the decorated in case he - // found a way to install his listener from another way than from self::listen() - $decorated = $this - ->decorated - ->get($listener) - ->match( - static fn($decorated) => $decorated, - static fn() => $listener, - ); - $this->signals->remove($decorated); - $this->decorated = $this->decorated->remove($decorated); - } -} diff --git a/src/CurrentProcess/Signals/Wrapper.php b/src/CurrentProcess/Signals/Wrapper.php deleted file mode 100644 index 3539de3..0000000 --- a/src/CurrentProcess/Signals/Wrapper.php +++ /dev/null @@ -1,37 +0,0 @@ -handler = $handler; - } - - public static function of(Handler $handler): self - { - return new self($handler); - } - - #[\Override] - public function listen(Signal $signal, callable $listener): void - { - $this->handler->listen($signal, $listener); - } - - #[\Override] - public function remove(callable $listener): void - { - $this->handler->remove($listener); - } -} diff --git a/src/Factory.php b/src/Factory.php index 932ea38..ee9cc5a 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -10,7 +10,7 @@ public static function build(?Config $config = null): OperatingSystem switch (\PHP_OS) { case 'Darwin': case 'Linux': - return OperatingSystem\Unix::of($config); + return OperatingSystem::new($config); } throw new \LogicException('Unuspported operating system'); diff --git a/src/Filesystem.php b/src/Filesystem.php index 85ce603..9b378ac 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -8,27 +8,106 @@ File\Content, }; use Innmind\Url\Path; -use Innmind\FileWatch\Ping; +use Innmind\Server\Control\Server\Processes; +use Innmind\FileWatch\{ + Ping, + Factory, + Watch, +}; use Innmind\Immutable\{ - Attempt, Maybe, - Str, + Attempt, Sequence, + Str, }; -interface Filesystem +final class Filesystem { + private Watch $watch; + private Config $config; + /** @var \WeakMap */ + private \WeakMap $mounted; + + private function __construct(Processes $processes, Config $config) + { + $this->watch = $config->fileWatch( + Factory::build($processes, $config->halt()), + ); + $this->config = $config; + /** @var \WeakMap */ + $this->mounted = new \WeakMap; + } + + /** + * @internal + */ + public static function of(Processes $processes, Config $config): self + { + return new self($processes, $config); + } + /** * @return Attempt */ - public function mount(Path $path): Attempt; - public function contains(Path $path): bool; + public function mount(Path $path): Attempt + { + /** + * @var Adapter $adapter + * @var string $mounted + */ + foreach ($this->mounted as $adapter => $mounted) { + if ($path->toString() === $mounted) { + return Attempt::result($adapter); + } + } + + return $this + ->config + ->filesystem($path) + ->map(function($adapter) use ($path) { + $this->mounted[$adapter] = $path->toString(); + + return $adapter; + }); + } + + public function contains(Path $path): bool + { + if (!\file_exists($path->toString())) { + return false; + } + + if ($path->directory() && !\is_dir($path->toString())) { + return false; + } + + return true; + } /** * @return Maybe Return the value returned by the file or nothing if the file doesn't exist */ - public function require(Path $path): Maybe; - public function watch(Path $path): Ping; + public function require(Path $path): Maybe + { + $path = $path->toString(); + + if (!\file_exists($path) || \is_dir($path)) { + /** @var Maybe */ + return Maybe::nothing(); + } + + /** + * @psalm-suppress UnresolvableInclude + * @psalm-suppress MixedArgument + * @var Maybe + */ + return Maybe::just(require $path); + } + + public function watch(Path $path): Ping + { + return ($this->watch)($path); + } /** * This method is to be used to generate a temporary file content even if it @@ -42,5 +121,24 @@ public function watch(Path $path): Ping; * * @return Attempt */ - public function temporary(Sequence $chunks): Attempt; + public function temporary(Sequence $chunks): Attempt + { + return $this + ->config + ->io() + ->files() + ->temporary(Sequence::of()) + ->flatMap( + static fn($tmp) => $chunks + ->sink($tmp->push()->chunk(...)) + ->attempt( + static fn($push, $chunk) => $chunk + ->attempt(static fn() => new \RuntimeException('Failed to load chunk')) + ->flatMap($push) + ->map(static fn() => $push), + ) + ->map(static fn() => $tmp->read()), + ) + ->map(Content::io(...)); + } } diff --git a/src/Filesystem/Generic.php b/src/Filesystem/Generic.php deleted file mode 100644 index f1f0aed..0000000 --- a/src/Filesystem/Generic.php +++ /dev/null @@ -1,136 +0,0 @@ - */ - private \WeakMap $mounted; - - private function __construct(Processes $processes, Config $config) - { - $this->watch = Factory::build($processes, $config->halt()); - $this->config = $config; - /** @var \WeakMap */ - $this->mounted = new \WeakMap; - } - - /** - * @internal - */ - public static function of(Processes $processes, Config $config): self - { - return new self($processes, $config); - } - - #[\Override] - public function mount(Path $path): Attempt - { - /** - * @var Adapter $adapter - * @var string $mounted - */ - foreach ($this->mounted as $adapter => $mounted) { - if ($path->toString() === $mounted) { - return Attempt::result($adapter); - } - } - - return Attempt::of(function() use ($path) { - $adapter = Adapter\Filesystem::mount( - $path, - $this->config->io(), - ) - ->withCaseSensitivity( - $this->config->filesystemCaseSensitivity(), - ); - $this->mounted[$adapter] = $path->toString(); - - return $adapter; - }); - } - - #[\Override] - public function contains(Path $path): bool - { - if (!\file_exists($path->toString())) { - return false; - } - - if ($path->directory() && !\is_dir($path->toString())) { - return false; - } - - return true; - } - - #[\Override] - public function require(Path $path): Maybe - { - $path = $path->toString(); - - if (!\file_exists($path) || \is_dir($path)) { - /** @var Maybe */ - return Maybe::nothing(); - } - - /** - * @psalm-suppress UnresolvableInclude - * @psalm-suppress MixedArgument - * @var Maybe - */ - return Maybe::just(require $path); - } - - #[\Override] - public function watch(Path $path): Ping - { - return ($this->watch)($path); - } - - #[\Override] - public function temporary(Sequence $chunks): Attempt - { - return $this - ->config - ->io() - ->files() - ->temporary(Sequence::of()) - ->flatMap( - static fn($tmp) => $chunks - ->sink($tmp->push()->chunk(...)) - ->attempt( - static fn($push, $chunk) => $chunk - ->attempt(static fn() => new \RuntimeException('Failed to load chunk')) - ->flatMap($push) - ->map(static fn() => $push), - ) - ->map(static fn() => $tmp->read()), - ) - ->map(Content::io(...)); - } -} diff --git a/src/Filesystem/Logger.php b/src/Filesystem/Logger.php deleted file mode 100644 index 6152594..0000000 --- a/src/Filesystem/Logger.php +++ /dev/null @@ -1,99 +0,0 @@ -filesystem = $filesystem; - $this->logger = $logger; - } - - public static function psr( - Filesystem $filesystem, - LoggerInterface $logger, - ): self { - return new self($filesystem, $logger); - } - - #[\Override] - public function mount(Path $path): Attempt - { - return $this->filesystem->mount($path)->map( - fn($adapter) => Adapter\Logger::psr( - $adapter, - $this->logger, - ), - ); - } - - #[\Override] - public function contains(Path $path): bool - { - $contains = $this->filesystem->contains($path); - - $this->logger->debug( - 'Checking if {path} exists, answer: {answer}', - ['answer' => $contains ? 'yes' : 'no'], - ); - - return $contains; - } - - #[\Override] - public function require(Path $path): Maybe - { - return $this - ->filesystem - ->require($path) - ->map(function(mixed $value) use ($path): mixed { - $this->logger->debug( - 'PHP file located at {path} loaded in memory', - ['path' => $path->toString()], - ); - - return $value; - }); - } - - #[\Override] - public function watch(Path $path): Ping - { - // todo bring back the ping logger - return $this->filesystem->watch($path); - } - - #[\Override] - public function temporary(Sequence $chunks): Attempt - { - return $this - ->filesystem - ->temporary($chunks) - ->map(function(Content $content) { - $this->logger->debug('Temporary file created'); - - return $content; - }); - } -} diff --git a/src/OperatingSystem.php b/src/OperatingSystem.php index 8e24a51..406f7d0 100644 --- a/src/OperatingSystem.php +++ b/src/OperatingSystem.php @@ -3,25 +3,103 @@ namespace Innmind\OperatingSystem; -use Innmind\Server\Status\Server as ServerStatus; -use Innmind\Server\Control\Server as ServerControl; +use Innmind\Server\Status\{ + Server as ServerStatus, + ServerFactory, +}; +use Innmind\Server\Control\{ + Server as ServerControl, + Servers, +}; use Innmind\TimeContinuum\Clock; -interface OperatingSystem +final class OperatingSystem { + private Config $config; + private ?Filesystem $filesystem = null; + private ?ServerStatus $status = null; + private ?ServerControl $control = null; + private ?Ports $ports = null; + private ?Sockets $sockets = null; + private ?Remote $remote = null; + private ?CurrentProcess $process = null; + + private function __construct(Config $config) + { + $this->config = $config; + } + + public static function new(?Config $config = null): self + { + return new self($config ?? Config::new()); + } + /** * This method allows to change the underlying OS implementation while being * able to keep any decorators on top of it. * - * @param callable(self, Config): self $map + * @param callable(Config): Config $map */ - public function map(callable $map): self; - public function clock(): Clock; - public function filesystem(): Filesystem; - public function status(): ServerStatus; - public function control(): ServerControl; - public function ports(): Ports; - public function sockets(): Sockets; - public function remote(): Remote; - public function process(): CurrentProcess; + public function map(callable $map): self + { + return new self($map($this->config)); + } + + public function clock(): Clock + { + return $this->config->clock(); + } + + public function filesystem(): Filesystem + { + return $this->filesystem ??= Filesystem::of( + $this->control()->processes(), + $this->config, + ); + } + + public function status(): ServerStatus + { + return $this->status ??= $this->config->serverStatus( + ServerFactory::build( + $this->clock(), + $this->control(), + $this->config->environmentPath(), + ), + ); + } + + public function control(): ServerControl + { + return $this->control ??= $this->config->serverControl( + Servers\Unix::of( + $this->clock(), + $this->config->io(), + $this->config->halt(), + ), + ); + } + + public function ports(): Ports + { + return $this->ports ??= Ports::of($this->config); + } + + public function sockets(): Sockets + { + return $this->sockets ??= Sockets::of($this->config); + } + + public function remote(): Remote + { + return $this->remote ??= Remote::of( + $this->control(), + $this->config, + ); + } + + public function process(): CurrentProcess + { + return $this->process ??= CurrentProcess::of($this->config->halt()); + } } diff --git a/src/OperatingSystem/Logger.php b/src/OperatingSystem/Logger.php deleted file mode 100644 index 900dc03..0000000 --- a/src/OperatingSystem/Logger.php +++ /dev/null @@ -1,115 +0,0 @@ -os = $os; - $this->logger = $logger; - } - - public static function psr(OperatingSystem $os, LoggerInterface $logger): self - { - return new self($os, $logger); - } - - #[\Override] - public function map(callable $map): OperatingSystem - { - return new self( - $this->os->map($map), - $this->logger, - ); - } - - #[\Override] - public function clock(): TimeContinuum\Clock - { - return TimeContinuum\Clock::logger( - $this->os->clock(), - $this->logger, - ); - } - - #[\Override] - public function filesystem(): Filesystem - { - return Filesystem\Logger::psr( - $this->os->filesystem(), - $this->logger, - ); - } - - #[\Override] - public function status(): Status\Server - { - return Status\Servers\Logger::of( - $this->os->status(), - $this->logger, - ); - } - - #[\Override] - public function control(): Control\Server - { - return Control\Servers\Logger::psr( - $this->os->control(), - $this->logger, - ); - } - - #[\Override] - public function ports(): Ports - { - return Ports\Logger::psr( - $this->os->ports(), - $this->logger, - ); - } - - #[\Override] - public function sockets(): Sockets - { - return Sockets\Logger::psr( - $this->os->sockets(), - $this->logger, - ); - } - - #[\Override] - public function remote(): Remote - { - return Remote\Logger::psr( - $this->os->remote(), - $this->logger, - ); - } - - #[\Override] - public function process(): CurrentProcess - { - return CurrentProcess\Logger::psr( - $this->os->process(), - $this->logger, - ); - } -} diff --git a/src/OperatingSystem/Resilient.php b/src/OperatingSystem/Resilient.php deleted file mode 100644 index b360067..0000000 --- a/src/OperatingSystem/Resilient.php +++ /dev/null @@ -1,91 +0,0 @@ -os = $os; - } - - public static function of(OperatingSystem $os): self - { - return new self($os); - } - - #[\Override] - public function map(callable $map): OperatingSystem - { - return new self($this->os->map($map)); - } - - #[\Override] - public function clock(): Clock - { - return $this->os->clock(); - } - - #[\Override] - public function filesystem(): Filesystem - { - return $this->os->filesystem(); - } - - #[\Override] - public function status(): ServerStatus - { - return $this->os->status(); - } - - #[\Override] - public function control(): ServerControl - { - return $this->os->control(); - } - - #[\Override] - public function ports(): Ports - { - return $this->os->ports(); - } - - #[\Override] - public function sockets(): Sockets - { - return $this->os->sockets(); - } - - #[\Override] - public function remote(): Remote - { - return Remote\Resilient::of( - $this->os->remote(), - $this->os->process(), - ); - } - - #[\Override] - public function process(): CurrentProcess - { - return $this->os->process(); - } -} diff --git a/src/OperatingSystem/Unix.php b/src/OperatingSystem/Unix.php deleted file mode 100644 index 04044ac..0000000 --- a/src/OperatingSystem/Unix.php +++ /dev/null @@ -1,113 +0,0 @@ -config = $config; - } - - public static function of(?Config $config = null): self - { - return new self($config ?? Config::of()); - } - - #[\Override] - public function map(callable $map): OperatingSystem - { - return $map($this, $this->config); - } - - #[\Override] - public function clock(): Clock - { - return $this->config->clock(); - } - - #[\Override] - public function filesystem(): Filesystem - { - return $this->filesystem ??= Filesystem\Generic::of( - $this->control()->processes(), - $this->config, - ); - } - - #[\Override] - public function status(): ServerStatus - { - return $this->status ??= ServerFactory::build( - $this->clock(), - $this->control(), - $this->config->environmentPath(), - ); - } - - #[\Override] - public function control(): ServerControl - { - return $this->control ??= Servers\Unix::of( - $this->clock(), - $this->config->io(), - $this->config->halt(), - ); - } - - #[\Override] - public function ports(): Ports - { - return $this->ports ??= Ports\Unix::of($this->config); - } - - #[\Override] - public function sockets(): Sockets - { - return $this->sockets ??= Sockets\Unix::of($this->config); - } - - #[\Override] - public function remote(): Remote - { - return $this->remote ??= Remote\Generic::of( - $this->control(), - $this->config, - ); - } - - #[\Override] - public function process(): CurrentProcess - { - return $this->process ??= CurrentProcess\Generic::of($this->config->halt()); - } -} diff --git a/src/Ports.php b/src/Ports.php index 4c3b5e1..5794216 100644 --- a/src/Ports.php +++ b/src/Ports.php @@ -11,10 +11,33 @@ use Innmind\IP\IP; use Innmind\Immutable\Attempt; -interface Ports +final class Ports { + private Config $config; + + private function __construct(Config $config) + { + $this->config = $config; + } + + /** + * @internal + */ + public static function of(Config $config): self + { + return new self($config); + } + /** * @return Attempt */ - public function open(Transport $transport, IP $ip, Port $port): Attempt; + public function open(Transport $transport, IP $ip, Port $port): Attempt + { + return $this + ->config + ->io() + ->sockets() + ->servers() + ->internet($transport, $ip, $port); + } } diff --git a/src/Ports/Logger.php b/src/Ports/Logger.php deleted file mode 100644 index 6891152..0000000 --- a/src/Ports/Logger.php +++ /dev/null @@ -1,46 +0,0 @@ -ports = $ports; - $this->logger = $logger; - } - - public static function psr(Ports $ports, LoggerInterface $logger): self - { - return new self($ports, $logger); - } - - #[\Override] - public function open(Transport $transport, IP $ip, Port $port): Attempt - { - $this->logger->debug( - 'Opening new port at {address}', - [ - 'address' => \sprintf( - '%s://%s:%s', - $transport->toString(), - $ip->toString(), - $port->toString(), - ), - ], - ); - - return $this->ports->open($transport, $ip, $port); - } -} diff --git a/src/Ports/Unix.php b/src/Ports/Unix.php deleted file mode 100644 index e131cfc..0000000 --- a/src/Ports/Unix.php +++ /dev/null @@ -1,42 +0,0 @@ -config = $config; - } - - /** - * @internal - */ - public static function of(Config $config): self - { - return new self($config); - } - - #[\Override] - public function open(Transport $transport, IP $ip, Port $port): Attempt - { - return $this - ->config - ->io() - ->sockets() - ->servers() - ->internet($transport, $ip, $port); - } -} diff --git a/src/Remote.php b/src/Remote.php index a97518d..e20ef77 100644 --- a/src/Remote.php +++ b/src/Remote.php @@ -3,7 +3,10 @@ namespace Innmind\OperatingSystem; -use Innmind\Server\Control\Server; +use Innmind\Server\Control\{ + Server, + Servers, +}; use Innmind\IO\{ Sockets\Clients\Client, Sockets\Internet\Transport, @@ -11,19 +14,70 @@ use Innmind\Url\{ Url, Authority, + Authority\Port, }; use Innmind\HttpTransport\Transport as HttpTransport; use Innmind\Immutable\Attempt; use Formal\AccessLayer\Connection; -interface Remote +final class Remote { - public function ssh(Url $server): Server; + private Server $server; + private Config $config; + private ?HttpTransport $http = null; + + private function __construct(Server $server, Config $config) + { + $this->server = $server; + $this->config = $config; + } + + /** + * @internal + */ + public static function of(Server $server, Config $config): self + { + return new self($server, $config); + } + + public function ssh(Url $server): Server + { + $port = null; + + if ($server->authority()->port()->value() !== Port::none()->value()) { + $port = $server->authority()->port(); + } + + return $this->config->serverControl( + Servers\Remote::of( + $this->server, + $server->authority()->userInformation()->user(), + $server->authority()->host(), + $port, + ), + ); + } /** * @return Attempt */ - public function socket(Transport $transport, Authority $authority): Attempt; - public function http(): HttpTransport; - public function sql(Url $server): Connection; + public function socket(Transport $transport, Authority $authority): Attempt + { + return $this + ->config + ->io() + ->sockets() + ->clients() + ->internet($transport, $authority); + } + + public function http(): HttpTransport + { + return $this->http ??= $this->config->httpTransport(); + } + + public function sql(Url $server): Connection + { + return $this->config->sql($server); + } } diff --git a/src/Remote/Generic.php b/src/Remote/Generic.php deleted file mode 100644 index 46c484a..0000000 --- a/src/Remote/Generic.php +++ /dev/null @@ -1,107 +0,0 @@ -server = $server; - $this->config = $config; - } - - /** - * @internal - */ - public static function of(Server $server, Config $config): self - { - return new self($server, $config); - } - - #[\Override] - public function ssh(Url $server): Server - { - $port = null; - - if ($server->authority()->port()->value() !== Port::none()->value()) { - $port = $server->authority()->port(); - } - - return Servers\Remote::of( - $this->server, - $server->authority()->userInformation()->user(), - $server->authority()->host(), - $port, - ); - } - - #[\Override] - public function socket(Transport $transport, Authority $authority): Attempt - { - return $this - ->config - ->io() - ->sockets() - ->clients() - ->internet($transport, $authority); - } - - #[\Override] - public function http(): HttpTransport - { - if ($this->http) { - return $this->http; - } - - $http = Curl::of( - $this->config->clock(), - $this->config->io(), - ); - $http = $this->config->maxHttpConcurrency()->match( - static fn($max) => $http->maxConcurrency($max), - static fn() => $http, - ); - $http = $this->config->httpHeartbeat()->match( - static fn($config) => $http->heartbeat($config[0], $config[1]), - static fn() => $http, - ); - $http = match ($this->config->mustDisableSSLVerification()) { - true => $http->disableSSLVerification(), - false => $http, - }; - - return $this->http = $http; - } - - #[\Override] - public function sql(Url $server): Connection - { - return Connection\Lazy::of(static fn() => Connection\PDO::of($server)); - } -} diff --git a/src/Remote/Logger.php b/src/Remote/Logger.php deleted file mode 100644 index 9189ce2..0000000 --- a/src/Remote/Logger.php +++ /dev/null @@ -1,77 +0,0 @@ -remote = $remote; - $this->logger = $logger; - } - - public static function psr(Remote $remote, LoggerInterface $logger): self - { - return new self($remote, $logger); - } - - #[\Override] - public function ssh(Url $server): Control\Server - { - return Control\Servers\Logger::psr( - $this->remote->ssh($server), - $this->logger, - ); - } - - #[\Override] - public function socket(Transport $transport, Authority $authority): Attempt - { - $this->logger->debug( - 'Opening remote socket at {address}', - [ - 'address' => \sprintf( - '%s://%s', - $transport->toString(), - $authority->toString(), - ), - ], - ); - - return $this->remote->socket($transport, $authority); - } - - #[\Override] - public function http(): HttpTransport\Transport - { - return HttpTransport\Logger::psr( - $this->remote->http(), - $this->logger, - ); - } - - #[\Override] - public function sql(Url $server): Connection - { - return Connection\Logger::psr( - $this->remote->sql($server), - $this->logger, - ); - } -} diff --git a/src/Remote/Resilient.php b/src/Remote/Resilient.php deleted file mode 100644 index 9f0a86a..0000000 --- a/src/Remote/Resilient.php +++ /dev/null @@ -1,78 +0,0 @@ -remote = $remote; - $this->process = $process; - } - - public static function of(Remote $remote, CurrentProcess $process): self - { - return new self($remote, $process); - } - - #[\Override] - public function ssh(Url $server): Server - { - return $this->remote->ssh($server); - } - - #[\Override] - public function socket(Transport $transport, Authority $authority): Attempt - { - return $this->remote->socket($transport, $authority); - } - - #[\Override] - public function http(): HttpTransport - { - return ExponentialBackoff::of( - $this->remote->http(), - new class($this->process) implements Halt { - public function __construct( - private CurrentProcess $process, - ) { - } - - #[\Override] - public function __invoke(Period $period): Attempt - { - return $this->process->halt($period); - } - }, - ); - } - - #[\Override] - public function sql(Url $server): Connection - { - return $this->remote->sql($server); - } -} diff --git a/src/Sockets.php b/src/Sockets.php index cb136e5..a743622 100644 --- a/src/Sockets.php +++ b/src/Sockets.php @@ -10,24 +10,63 @@ }; use Innmind\Immutable\Attempt; -interface Sockets +final class Sockets { + private Config $config; + + private function __construct(Config $config) + { + $this->config = $config; + } + + /** + * @internal + */ + public static function of(Config $config): self + { + return new self($config); + } + /** * This method will fail if the socket already exist * * @return Attempt */ - public function open(Address $address): Attempt; + public function open(Address $address): Attempt + { + return $this + ->config + ->io() + ->sockets() + ->servers() + ->unix($address); + } /** * This will take control of the socket if it already exist (use carefully) * * @return Attempt */ - public function takeOver(Address $address): Attempt; + public function takeOver(Address $address): Attempt + { + return $this + ->config + ->io() + ->sockets() + ->servers() + ->takeOver($address); + } /** * @return Attempt */ - public function connectTo(Address $address): Attempt; + public function connectTo(Address $address): Attempt + { + return $this + ->config + ->io() + ->sockets() + ->clients() + ->unix($address); + } } diff --git a/src/Sockets/Logger.php b/src/Sockets/Logger.php deleted file mode 100644 index 9ba4cf0..0000000 --- a/src/Sockets/Logger.php +++ /dev/null @@ -1,59 +0,0 @@ -sockets = $sockets; - $this->logger = $logger; - } - - public static function psr(Sockets $sockets, LoggerInterface $logger): self - { - return new self($sockets, $logger); - } - - #[\Override] - public function open(Address $address): Attempt - { - $this->logger->debug( - 'Opening socket at {address}', - ['address' => $address->toString()], - ); - - return $this->sockets->open($address); - } - - #[\Override] - public function takeOver(Address $address): Attempt - { - $this->logger->debug( - 'Taking over the socket at {address}', - ['address' => $address->toString()], - ); - - return $this->sockets->takeOver($address); - } - - #[\Override] - public function connectTo(Address $address): Attempt - { - $this->logger->debug( - 'Connecting to socket at {address}', - ['address' => $address->toString()], - ); - - return $this->sockets->connectTo($address); - } -} diff --git a/src/Sockets/Unix.php b/src/Sockets/Unix.php deleted file mode 100644 index 03ee3da..0000000 --- a/src/Sockets/Unix.php +++ /dev/null @@ -1,62 +0,0 @@ -config = $config; - } - - /** - * @internal - */ - public static function of(Config $config): self - { - return new self($config); - } - - #[\Override] - public function open(Address $address): Attempt - { - return $this - ->config - ->io() - ->sockets() - ->servers() - ->unix($address); - } - - #[\Override] - public function takeOver(Address $address): Attempt - { - return $this - ->config - ->io() - ->sockets() - ->servers() - ->takeOver($address); - } - - #[\Override] - public function connectTo(Address $address): Attempt - { - return $this - ->config - ->io() - ->sockets() - ->clients() - ->unix($address); - } -} diff --git a/tests/CurrentProcess/GenericTest.php b/tests/CurrentProcess/GenericTest.php deleted file mode 100644 index 30e825a..0000000 --- a/tests/CurrentProcess/GenericTest.php +++ /dev/null @@ -1,68 +0,0 @@ -assertInstanceOf( - CurrentProcess::class, - Generic::of(Halt\Usleep::new()), - ); - } - - public function testId() - { - $process = Generic::of(Halt\Usleep::new()); - - $this->assertInstanceOf(Pid::class, $process->id()->unwrap()); - $this->assertSame( - $process->id()->unwrap()->toInt(), - $process->id()->unwrap()->toInt(), - ); - } - - public function testHalt() - { - $process = Generic::of( - Halt\Usleep::new(), - ); - - $this->assertInstanceOf( - SideEffect::class, - $process - ->halt(Period::millisecond(1)) - ->unwrap(), - ); - } - - public function testSignals() - { - $process = Generic::of(Halt\Usleep::new()); - - $this->assertInstanceOf(Signals\Wrapper::class, $process->signals()); - $this->assertSame($process->signals(), $process->signals()); - } - - public function testMemory() - { - $process = Generic::of(Halt\Usleep::new()); - - $this->assertInstanceOf(Bytes::class, $process->memory()); - $this->assertGreaterThan(3_000_000, $process->memory()->toInt()); // ~3MB - } -} diff --git a/tests/CurrentProcess/Signals/WrapperTest.php b/tests/CurrentProcess/SignalsTest.php similarity index 79% rename from tests/CurrentProcess/Signals/WrapperTest.php rename to tests/CurrentProcess/SignalsTest.php index 304b96b..a3130ec 100644 --- a/tests/CurrentProcess/Signals/WrapperTest.php +++ b/tests/CurrentProcess/SignalsTest.php @@ -1,31 +1,20 @@ assertInstanceOf( - Signals::class, - Wrapper::of(new Handler), - ); - } - public function testListen() { - $signals = Wrapper::of(new Handler); + $signals = Signals::of(new Handler); $order = []; $count = 0; @@ -50,7 +39,7 @@ public function testListen() public function testRemoveSignal() { - $signals = Wrapper::of(new Handler); + $signals = Signals::of(new Handler); $order = []; $count = 0; diff --git a/tests/CurrentProcessTest.php b/tests/CurrentProcessTest.php new file mode 100644 index 0000000..5b819d6 --- /dev/null +++ b/tests/CurrentProcessTest.php @@ -0,0 +1,73 @@ +assertInstanceOf(Pid::class, $process->id()->unwrap()); + $this->assertSame( + $process->id()->unwrap()->toInt(), + $process->id()->unwrap()->toInt(), + ); + } + + public function testHalt(): BlackBox\Proof + { + return $this + ->forAll(Set::of( + OperatingSystem::new(), + OperatingSystem::new(Config::new()->map(Config\Logger::psr(new NullLogger))), + )) + ->prove(function($os) { + $process = $os->process(); + + $this->assertInstanceOf( + SideEffect::class, + $process + ->halt(Period::millisecond(1)) + ->unwrap(), + ); + }); + } + + public function testSignals() + { + $process = CurrentProcess::of(Halt\Usleep::new()); + + $this->assertInstanceOf(Signals::class, $process->signals()); + $this->assertSame($process->signals(), $process->signals()); + } + + public function testMemory() + { + $process = CurrentProcess::of(Halt\Usleep::new()); + + $this->assertInstanceOf(Bytes::class, $process->memory()); + $this->assertGreaterThan(3_000_000, $process->memory()->toInt()); // ~3MB + } +} diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 2dc8569..1df7a75 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -5,16 +5,19 @@ use Innmind\OperatingSystem\{ Factory, - OperatingSystem\Unix, + OperatingSystem, Config, }; use Innmind\TimeContinuum\Clock; use Innmind\Filesystem\{ + Adapter\Filesystem, File, File\Content, Directory, + CaseSensitivity, }; use Innmind\Url\Path; +use Innmind\Immutable\Attempt; use Symfony\Component\Filesystem\Filesystem as FS; use Innmind\BlackBox\PHPUnit\Framework\TestCase; @@ -24,9 +27,9 @@ public function testBuild() { $clock = Clock::live(); - $os = Factory::build(Config::of()->withClock($clock)); + $os = Factory::build(Config::new()->withClock($clock)); - $this->assertInstanceOf(Unix::class, $os); + $this->assertInstanceOf(OperatingSystem::class, $os); $this->assertSame($clock, $os->clock()); } @@ -48,7 +51,16 @@ public function testPersistingFileOnCaseInsensitiveFilesystem() $path = \sys_get_temp_dir().'/innmind/filesystem/'; (new FS)->remove($path); - $os = Factory::build(Config::of()->caseInsensitiveFilesystem()); + $os = Factory::build( + Config::new()->mountFilesystemVia( + static fn($path, $config) => Attempt::of( + static fn() => Filesystem::mount( + $path, + $config->io(), + )->withCaseSensitivity(CaseSensitivity::insensitive), + ), + ), + ); $adapter = $os ->filesystem() ->mount(Path::of($path)) diff --git a/tests/Filesystem/GenericTest.php b/tests/FilesystemTest.php similarity index 82% rename from tests/Filesystem/GenericTest.php rename to tests/FilesystemTest.php index 3b604e6..20b4ae6 100644 --- a/tests/Filesystem/GenericTest.php +++ b/tests/FilesystemTest.php @@ -1,10 +1,9 @@ assertInstanceOf( - Filesystem::class, - Generic::of( - Factory::build()->control()->processes(), - Config::of(), - ), - ); - } - public function testMount() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $adapter = $filesystem->mount(Path::of('/tmp/'))->unwrap(); @@ -56,9 +44,9 @@ public function testMount() public function testMountingTheSamePathTwiceReturnsTheSameAdapter() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $adapter = $filesystem->mount(Path::of('/tmp/'))->unwrap(); @@ -68,9 +56,9 @@ public function testMountingTheSamePathTwiceReturnsTheSameAdapter() public function testContainsFile() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $this->assertFalse($filesystem->contains(Path::of('/tmp/foo'))); @@ -81,9 +69,9 @@ public function testContainsFile() public function testContainsDirectory() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $this->assertFalse($filesystem->contains(Path::of('/tmp/some-dir/'))); @@ -94,9 +82,9 @@ public function testContainsDirectory() public function testWatch() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $this->assertInstanceOf(Ping::class, $filesystem->watch(Path::of('/somewhere'))); @@ -107,9 +95,9 @@ public function testRequireUnknownFile(): BlackBox\Proof return $this ->forAll(FPath::any()) ->prove(function($path) { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $this->assertFalse($filesystem->require($path)->match( @@ -121,9 +109,9 @@ public function testRequireUnknownFile(): BlackBox\Proof public function testRequireFile() { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $this->assertSame(42, $filesystem->require(Path::of(__DIR__.'/fixture.php'))->match( @@ -137,9 +125,9 @@ public function testCreateTemporaryFile() $this ->forAll(Set::sequence(Set::strings()->unicode())) ->then(function($chunks) { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $content = $filesystem @@ -169,9 +157,9 @@ public function testCreateTemporaryFileFailure() Set::sequence(Set::strings()->unicode())->between(0, 20), // upper bound to fit in memory ) ->then(function($leading, $trailing) { - $filesystem = Generic::of( + $filesystem = Filesystem::of( Factory::build()->control()->processes(), - Config::of(), + Config::new(), ); $content = $filesystem diff --git a/tests/OperatingSystem/ResilientTest.php b/tests/OperatingSystem/ResilientTest.php deleted file mode 100644 index 9dba327..0000000 --- a/tests/OperatingSystem/ResilientTest.php +++ /dev/null @@ -1,53 +0,0 @@ -assertInstanceOf(OperatingSystem::class, $os); - $this->assertInstanceOf(Clock::class, $os->clock()); - $this->assertInstanceOf(Filesystem::class, $os->filesystem()); - $this->assertInstanceOf(ServerStatus::class, $os->status()); - $this->assertInstanceOf(ServerControl::class, $os->control()); - $this->assertInstanceOf(Ports::class, $os->ports()); - $this->assertInstanceOf(Sockets::class, $os->sockets()); - $this->assertInstanceOf(Remote\Resilient::class, $os->remote()); - $this->assertInstanceOf(CurrentProcess::class, $os->process()); - } - - public function testMap() - { - $underlying = Unix::of(); - $os = Resilient::of($underlying); - - $result = $os->map(function($os) use ($underlying) { - $this->assertSame($underlying, $os); - - return Unix::of(); - }); - - $this->assertInstanceOf(Resilient::class, $result); - $this->assertNotSame($os, $result); - } -} diff --git a/tests/OperatingSystem/UnixTest.php b/tests/OperatingSystemTest.php similarity index 56% rename from tests/OperatingSystem/UnixTest.php rename to tests/OperatingSystemTest.php index f55e1e8..6500da9 100644 --- a/tests/OperatingSystem/UnixTest.php +++ b/tests/OperatingSystemTest.php @@ -1,10 +1,9 @@ withClock($clock)); + $os = OperatingSystem::new(Config::new()->withClock($clock)); - $this->assertInstanceOf(OperatingSystem::class, $os); $this->assertSame($clock, $os->clock()); - $this->assertInstanceOf(Filesystem\Generic::class, $os->filesystem()); + $this->assertInstanceOf(Filesystem::class, $os->filesystem()); $this->assertInstanceOf(ServerStatus::class, $os->status()); $this->assertInstanceOf(ServerControl::class, $os->control()); - $this->assertInstanceOf(Ports\Unix::class, $os->ports()); - $this->assertInstanceOf(Sockets\Unix::class, $os->sockets()); - $this->assertInstanceOf(Remote\Generic::class, $os->remote()); - $this->assertInstanceOf(CurrentProcess\Generic::class, $os->process()); + $this->assertInstanceOf(Ports::class, $os->ports()); + $this->assertInstanceOf(Sockets::class, $os->sockets()); + $this->assertInstanceOf(Remote::class, $os->remote()); + $this->assertInstanceOf(CurrentProcess::class, $os->process()); $this->assertSame($os->filesystem(), $os->filesystem()); $this->assertSame($os->status(), $os->status()); $this->assertSame($os->control(), $os->control()); @@ -46,16 +44,14 @@ public function testInterface() public function testMap() { - $os = Unix::of($config = Config::of()); - $expected = Unix::of(); + $os = OperatingSystem::new($config = Config::new()); - $result = $os->map(function($os_, $config_) use ($os, $config, $expected) { - $this->assertSame($os, $os_); + $result = $os->map(function($config_) use ($config) { $this->assertSame($config, $config_); - return $expected; + return Config::new(); }); - $this->assertSame($expected, $result); + $this->assertNotSame($os, $result); } } diff --git a/tests/Ports/UnixTest.php b/tests/PortsTest.php similarity index 71% rename from tests/Ports/UnixTest.php rename to tests/PortsTest.php index e967b39..a0db87d 100644 --- a/tests/Ports/UnixTest.php +++ b/tests/PortsTest.php @@ -1,10 +1,9 @@ assertInstanceOf(Ports::class, Unix::of(Config::of())); - } - public function testOpen() { - $ports = Unix::of(Config::of()); + $ports = Ports::of(Config::new()); $socket = $ports->open( Transport::tlsv12(), diff --git a/tests/Remote/ResilientTest.php b/tests/Remote/ResilientTest.php index 1a76171..6a9f805 100644 --- a/tests/Remote/ResilientTest.php +++ b/tests/Remote/ResilientTest.php @@ -4,29 +4,14 @@ namespace Tests\Innmind\OperatingSystem\Remote; use Innmind\OperatingSystem\{ - Remote\Resilient, - Remote, + Config, Factory, }; use Innmind\HttpTransport\ExponentialBackoff; -use Innmind\Server\Control\Server; -use Innmind\IO\Sockets\Internet\Transport; -use Innmind\Immutable\Attempt; -use Formal\AccessLayer\Connection; -use Innmind\BlackBox\{ - PHPUnit\BlackBox, - PHPUnit\Framework\TestCase, - Set, -}; -use Fixtures\Innmind\Url\{ - Url, - Authority, -}; +use Innmind\BlackBox\PHPUnit\Framework\TestCase; class ResilientTest extends TestCase { - use BlackBox; - private $os; public function setUp(): void @@ -34,81 +19,12 @@ public function setUp(): void $this->os = Factory::build(); } - public function testInterface() - { - $this->assertInstanceOf( - Remote::class, - Resilient::of( - $this->os->remote(), - $this->os->process(), - ), - ); - } - - public function testSsh(): BlackBox\Proof - { - return $this - ->forAll(Url::any()) - ->prove(function($url) { - $remote = Resilient::of( - $this->os->remote(), - $this->os->process(), - ); - - $this->assertInstanceOf( - Server::class, - $remote->ssh($url), - ); - }); - } - - public function testSocket(): BlackBox\Proof - { - return $this - ->forAll( - Set::of( - Transport::tcp(), - Transport::ssl(), - Transport::tls(), - Transport::tlsv10(), - Transport::tlsv12(), - ), - Authority::any(), - ) - ->prove(function($transport, $authority) { - $remote = Resilient::of( - $this->os->remote(), - $this->os->process(), - ); - - $this->assertInstanceOf( - Attempt::class, - $remote->socket($transport, $authority), - ); - }); - } - public function testHttp() { - $remote = Resilient::of( - $this->os->remote(), - $this->os->process(), + $os = $this->os->map( + Config\Resilient::new(), ); - $this->assertInstanceOf(ExponentialBackoff::class, $remote->http()); - } - - public function testSql(): BlackBox\Proof - { - return $this - ->forAll(Url::any()) - ->prove(function($server) { - $remote = Resilient::of( - $this->os->remote(), - $this->os->process(), - ); - - $this->assertInstanceOf(Connection::class, $remote->sql($server)); - }); + $this->assertInstanceOf(ExponentialBackoff::class, $os->remote()->http()); } } diff --git a/tests/Remote/GenericTest.php b/tests/RemoteTest.php similarity index 71% rename from tests/Remote/GenericTest.php rename to tests/RemoteTest.php index 60429c4..c9a5d1d 100644 --- a/tests/Remote/GenericTest.php +++ b/tests/RemoteTest.php @@ -1,11 +1,11 @@ assertInstanceOf( - Remote::class, - Generic::of( - $this->server(), - Config::of(), - ), + $remote = Remote::of( + $this->server("ssh '-p' '42' 'user@my-vps' 'ls'"), + Config::new(), ); + + $remoteServer = $remote->ssh(Url::of('ssh://user@my-vps:42/')); + + $this->assertInstanceOf(Servers\Remote::class, $remoteServer); + $remoteServer + ->processes() + ->execute(Command::foreground('ls')) + ->unwrap(); } - public function testSsh() + public function testSshLogger() { - $remote = Generic::of( + $remote = Remote::of( $this->server("ssh '-p' '42' 'user@my-vps' 'ls'"), - Config::of(), + Config::new()->map(Config\Logger::psr(new NullLogger)), ); $remoteServer = $remote->ssh(Url::of('ssh://user@my-vps:42/')); - $this->assertInstanceOf(Servers\Remote::class, $remoteServer); + $this->assertInstanceOf(Servers\Logger::class, $remoteServer); $remoteServer ->processes() ->execute(Command::foreground('ls')) @@ -69,9 +76,9 @@ public function testSsh() public function testSshWithoutPort() { - $remote = Generic::of( + $remote = Remote::of( $this->server("ssh 'user@my-vps' 'ls'"), - Config::of(), + Config::new(), ); $remoteServer = $remote->ssh(Url::of('ssh://user@my-vps/')); @@ -82,9 +89,9 @@ public function testSshWithoutPort() public function testSocket() { - $remote = Generic::of( + $remote = Remote::of( $this->server(), - Config::of(), + Config::new(), ); $server = Factory::build() ->ports() @@ -104,30 +111,34 @@ public function testSocket() $socket->close(); } - public function testHttp() + public function testHttp(): BlackBox\Proof { - $remote = Generic::of( - $this->server(), - Config::of(), - ); - - $http = $remote->http(); - - $this->assertInstanceOf(HttpTransport::class, $http); - $this->assertSame($http, $remote->http()); + return $this + ->forAll(Set::of( + OperatingSystem::new(), + OperatingSystem::new(Config::new()->map(Config\Logger::psr(new NullLogger))), + )) + ->prove(function($os) { + $remote = $os->remote(); + $http = $remote->http(); + + $this->assertInstanceOf(HttpTransport::class, $http); + $this->assertSame($http, $remote->http()); + }); } public function testSql(): BlackBox\Proof { return $this - ->forAll(FUrl::any()) - ->prove(function($server) { - $remote = Generic::of( - $this->server(), - Config::of(), - ); - - $sql = $remote->sql($server); + ->forAll( + FUrl::any(), + Set::of( + OperatingSystem::new(), + OperatingSystem::new(Config::new()->map(Config\Logger::psr(new NullLogger))), + ), + ) + ->prove(function($server, $os) { + $sql = $os->remote()->sql($server); $this->assertInstanceOf(Connection::class, $sql); }); diff --git a/tests/Sockets/UnixTest.php b/tests/SocketsTest.php similarity index 83% rename from tests/Sockets/UnixTest.php rename to tests/SocketsTest.php index 6cc61d2..29039f6 100644 --- a/tests/Sockets/UnixTest.php +++ b/tests/SocketsTest.php @@ -1,10 +1,9 @@ assertInstanceOf(Sockets::class, Unix::of(Config::of())); - } - public function testOpen() { - $sockets = Unix::of(Config::of()); + $sockets = Sockets::of(Config::new()); $socket = $sockets->open(Address::of(Path::of('/tmp/foo')))->match( static fn($server) => $server, @@ -44,7 +38,7 @@ public function testOpen() public function testTakeOver() { - $sockets = Unix::of(Config::of()); + $sockets = Sockets::of(Config::new()); $socket = $sockets->open(Address::of(Path::of('/tmp/foo')))->match( static fn($server) => $server, @@ -62,7 +56,7 @@ public function testTakeOver() public function testConnectTo() { - $sockets = Unix::of(Config::of()); + $sockets = Sockets::of(Config::new()); $server = $sockets->open(Address::of(Path::of('/tmp/foo')))->match( static fn($server) => $server, diff --git a/tests/Filesystem/fixture.php b/tests/fixture.php similarity index 100% rename from tests/Filesystem/fixture.php rename to tests/fixture.php