From dc1f030e3904677cd65eac0a90c1d850e0ad1bbf Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 31 Aug 2026 00:07:36 +0900 Subject: feat(plugin): serve HttpDownloader as a proxy stub A downloader carries its own options, TLS defaults and request backends, and what it shares with the graph is the IO it collects authentication into and the config it reads. Plugin code writing `new HttpDownloader($io, $config)` therefore allocates a Rust-side entity of its own rather than a second downloader the graph knows nothing about, and the guard that shadowed the class in the worker is gone. The request surface is not served yet: get() and copy() have no wire representation for the Response they return, and the async surface resolves its promises with one. Co-Authored-By: Claude Opus 5 (1M context) --- .../php/stubs/Composer/Util/HttpDownloader.php | 216 +++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 crates/shirabe-php-rpc/php/stubs/Composer/Util/HttpDownloader.php (limited to 'crates/shirabe-php-rpc/php/stubs/Composer') diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Util/HttpDownloader.php b/crates/shirabe-php-rpc/php/stubs/Composer/Util/HttpDownloader.php new file mode 100644 index 00000000..c29b1abb --- /dev/null +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Util/HttpDownloader.php @@ -0,0 +1,216 @@ +__rhandle = $rhandle; + $this->__epoch = $epoch; + } + + public function __destruct() + { + \ShirabeRustObjectRegistry::release($this->__rhandle); + } + + public function __shirabeRustHandleDescriptor(): array + { + return [ + '__rhandle' => $this->__rhandle, + '__class' => static::class, + '__epoch' => $this->__epoch, + ]; + } + + public function __clone() + { + // PHP has already shallow-copied this stub, so both copies would point at one + // entity and release it twice. The Rust side clones the entity instead, applying + // whatever __clone semantics the real class defines, and this copy rebinds to the + // fresh handle. Entities without clone semantics answer with an explicit error. + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust($this->__rhandle, '__shirabeClone', []); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + + public function __get($name) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, '__get', [$name]); + } + + public function __set($name, $value): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, '__set', [$name, $value]); + } + + public function __isset($name): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, '__isset', [$name]); + } + + public function __unset($name): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, '__unset', [$name]); + } + + public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false) + { + [$this->__rhandle, $this->__epoch] = \ShirabeRpcRuntime::callRust(0, '__shirabeConstruct', [static::class, [$io, $config, $options, $disableTls]]); + \ShirabeRustObjectRegistry::adopt($this->__rhandle, $this); + } + + private const STATUS_QUEUED = 1; + private const STATUS_STARTED = 2; + private const STATUS_COMPLETED = 3; + private const STATUS_FAILED = 4; + private const STATUS_ABORTED = 5; + + public static function outputWarnings(IOInterface $io, string $url, $data): void + { + $cleanMessage = static function ($msg) use ($io) { + if (!$io->isDecorated()) { + $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg); + } + + return $msg; + }; + + // legacy warning/info keys + foreach (['warning', 'info'] as $type) { + if (empty($data[$type])) { + continue; + } + + if (!empty($data[$type . '-versions'])) { + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($data[$type . '-versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).''); + } + + // modern Composer 2.2+ format with support for multiple warning/info messages + foreach (['warnings', 'infos'] as $key) { + if (empty($data[$key])) { + continue; + } + + $versionParser = new VersionParser(); + foreach ($data[$key] as $spec) { + $type = substr($key, 0, -1); + $constraint = $versionParser->parseConstraints($spec['versions']); + $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion())); + if (!$constraint->matches($composer)) { + continue; + } + + $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).''); + } + } + } + + public static function getExceptionHints(\Throwable $e): ?array + { + if (!$e instanceof TransportException) { + return null; + } + + if ( + false !== strpos($e->getMessage(), 'Resolving timed out') + || false !== strpos($e->getMessage(), 'Could not resolve host') + ) { + Silencer::suppress(); + $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([ + 'ssl' => ['verify_peer' => false], + 'http' => ['follow_location' => false, 'ignore_errors' => true], + ])); + Silencer::restore(); + if (false !== $testConnectivity) { + return [ + 'The following exception probably indicates you have misconfigured DNS resolver(s)', + ]; + } + + return [ + 'The following exception probably indicates you are offline or have misconfigured DNS resolver(s)', + ]; + } + + return null; + } + + public static function isCurlEnabled(): bool + { + return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init'); + } + + public function get(string $url, array $options = []) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'get', [$url, $options]); + } + + public function add(string $url, array $options = []) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'add', [$url, $options]); + } + + public function copy(string $url, string $to, array $options = []) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'copy', [$url, $to, $options]); + } + + public function addCopy(string $url, string $to, array $options = []) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'addCopy', [$url, $to, $options]); + } + + public function getOptions() + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getOptions', []); + } + + public function setOptions(array $options) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'setOptions', [$options]); + } + + public function wait(?int $index = null) + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'wait', [$index]); + } + + public function enableAsync(): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, 'enableAsync', []); + } + + public function countActiveJobs(?int $index = null): int + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'countActiveJobs', [$index]); + } +} -- cgit v1.3.1-4-g156e