diff --git a/README.md b/README.md index 26bab6b..4100fe8 100644 --- a/README.md +++ b/README.md @@ -39,19 +39,54 @@ composer require internal/dload -W ./vendor/bin/dload get dolt ``` -### Configure preset for the project (WIP) +### Configure preset for the project Create `dload.xml` file in the root of the project with the following content: ```xml - + + + + ``` -Download all the software from the preset: +There are two software packages to download: `temporal` and `rr` with version `^2.12.0`. +Optionally, you may specify the version of the software package using Composer versioning syntax. +To download all the configured software, run `dload get` without arguments: ```bash ./vendor/bin/dload get ``` + +### Custom software registry + +```xml + + + + + + + + + +``` + +### GitHub Token + +To increase the rate limit for GitHub API, you can specify the token in the environment variable `GITHUB_TOKEN`: + +```bash +GITHUB_TOKEN=your_token_here ./vendor/bin/dload get +``` diff --git a/dload.xml b/dload.xml index 55ea365..c416903 100644 --- a/dload.xml +++ b/dload.xml @@ -1,17 +1,19 @@ - - - + + + + + + + + + - - - - - - - - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 54e8b20..983c3b9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,25 +1,70 @@ + + + + + + + + + + + + + $toDownload[$software] + ?? DownloadConfig::fromSoftwareId((string) $software), + $input->getArgument(self::ARG_SOFTWARE), + )]]> + - + getArgument(self::ARG_SOFTWARE)]]> + + + - + - - getArgument('binary')]]> - getOption('path')]]> - + + ]]> + - - + + + + cancelling]]> + + + + + + + + type]]> + uri]]> + + + + + + + repositories]]> + + + homepage]]> + pattern]]> + + files]]> + @@ -34,12 +79,33 @@ + + + + + + + + + File::fromArray($fileArray), + $softwareArray['files'] ?? [], + )]]> + Repository::fromArray($repositoryArray), + $softwareArray['repositories'] ?? [], + )]]> + + + + + @@ -69,10 +135,16 @@ repoConfig->assetPattern]]> + + + + + + @@ -80,6 +152,12 @@ ($context->onProgress)(]]> + + repoConfig]]> + + + repoConfig]]> + @@ -92,6 +170,15 @@ + + + + + + + + + addOption('config', null, InputOption::VALUE_OPTIONAL, 'Path to the configuration file'); + } + protected function execute( InputInterface $input, OutputInterface $output, ): int { $this->logger = new Logger($output); $this->container = $container = Bootstrap::init()->withConfig( - xml: \dirname(__DIR__, 2) . '/dload.xml', + xml: $this->getConfigFile($input), inputOptions: $input->getOptions(), inputArguments: $input->getArguments(), environment: \getenv(), )->finish(); + $container->set($input, InputInterface::class); $container->set($output, OutputInterface::class); $container->set(new SymfonyStyle($input, $output), StyleInterface::class); @@ -40,4 +48,25 @@ protected function execute( return Command::SUCCESS; } + + /** + * @return non-empty-string|null Path to the configuration file + */ + private function getConfigFile(InputInterface $input): ?string + { + /** @var string|null $config */ + $config = $input->getOption('config'); + $isConfigured = $config !== null; + $config ??= './dload.xml'; + + if (\is_file($config)) { + return $config; + } + + $isConfigured and throw new \InvalidArgumentException( + 'Configuration file not found: ' . $config, + ); + + return null; + } } diff --git a/src/Command/Get.php b/src/Command/Get.php index deb4888..c5284cf 100644 --- a/src/Command/Get.php +++ b/src/Command/Get.php @@ -6,6 +6,8 @@ use Internal\DLoad\DLoad; use Internal\DLoad\Module\Common\Architecture; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; +use Internal\DLoad\Module\Common\Config\Actions; use Internal\DLoad\Module\Common\OperatingSystem; use Internal\DLoad\Module\Common\Stability; use Symfony\Component\Console\Attribute\AsCommand; @@ -25,9 +27,12 @@ )] final class Get extends Base implements SignalableCommandInterface { + private const ARG_SOFTWARE = 'software'; + public function configure(): void { - $this->addArgument('binary', InputArgument::REQUIRED, 'Binary name, e.g. "rr", "dolt", "temporal" etc.'); + parent::configure(); + $this->addArgument(self::ARG_SOFTWARE, InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Software name, e.g. "rr", "dolt", "temporal" etc.'); $this->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to store the binary, e.g. "./bin"', "."); $this->addOption('arch', null, InputOption::VALUE_OPTIONAL, 'Architecture, e.g. "amd64", "arm64" etc.'); $this->addOption('os', null, InputOption::VALUE_OPTIONAL, 'Operating system, e.g. "linux", "darwin" etc.'); @@ -61,22 +66,49 @@ public function getSubscribedSignals(): array protected function execute(InputInterface $input, OutputInterface $output): int { parent::execute($input, $output); - $container = $this->container; - $output->writeln('Binary to load: ' . $input->getArgument('binary')); - $output->writeln('Path to store the binary: ' . $input->getOption('path')); + /** @var Actions $actionsConfig */ + $actionsConfig = $container->get(Actions::class); + $actions = $this->getDownloadActions($input, $actionsConfig); + $output->writeln('Architecture: ' . $container->get(Architecture::class)->name); $output->writeln(' Op. system: ' . $container->get(OperatingSystem::class)->name); $output->writeln(' Stability: ' . $container->get(Stability::class)->name); + $actions === [] and throw new \RuntimeException('No software to download.'); + /** @var DLoad $dload */ $dload = $container->get(DLoad::class); - $binary = $input->getArgument('binary'); - $dload->addTask($binary); + foreach ($actions as $action) { + $dload->addTask($action); + } $dload->run(); return Command::SUCCESS; } + + /** + * @return list + */ + private function getDownloadActions(InputInterface $input, Actions $actionsConfig): array + { + $argument = $input->getArgument(self::ARG_SOFTWARE); + if ($argument === []) { + // Use configured actions if CLI arguments are empty + return $actionsConfig->downloads; + } + + $toDownload = []; + foreach ($actionsConfig->downloads as $action) { + $toDownload[$action->software] = $action; + } + + return \array_map( + static fn(mixed $software): DownloadConfig => $toDownload[$software] + ?? DownloadConfig::fromSoftwareId((string) $software), + $input->getArgument(self::ARG_SOFTWARE), + ); + } } diff --git a/src/Command/ListSoftware.php b/src/Command/ListSoftware.php index 5edce10..f2a211f 100644 --- a/src/Command/ListSoftware.php +++ b/src/Command/ListSoftware.php @@ -35,6 +35,7 @@ protected function execute( /** @var Software $software */ foreach ($registry->getIterator() as $software) { $output->writeln("{$software->getId()} $software->name"); + $software->homepage and $output->writeln("Homepage: $software->homepage"); foreach ($software->repositories as $repo) { $output->writeln("{$repo->type}: {$repo->uri}"); diff --git a/src/DLoad.php b/src/DLoad.php index 2670b57..cabd90a 100644 --- a/src/DLoad.php +++ b/src/DLoad.php @@ -5,14 +5,16 @@ namespace Internal\DLoad; use Internal\DLoad\Module\Archive\ArchiveFactory; -use Internal\DLoad\Module\Common\Config\Destination; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; use Internal\DLoad\Module\Common\Config\Embed\File; use Internal\DLoad\Module\Common\Config\Embed\Software; +use Internal\DLoad\Module\Common\Input\Destination; use Internal\DLoad\Module\Downloader\Downloader; use Internal\DLoad\Module\Downloader\SoftwareCollection; use Internal\DLoad\Module\Downloader\Task\DownloadResult; use Internal\DLoad\Module\Downloader\Task\DownloadTask; use Internal\DLoad\Module\Downloader\TaskManager; +use Internal\DLoad\Service\Logger; use React\Promise\PromiseInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; @@ -29,6 +31,7 @@ final class DLoad public bool $useMock = false; public function __construct( + private readonly Logger $logger, private readonly TaskManager $taskManager, private readonly SoftwareCollection $softwareCollection, private readonly Downloader $downloader, @@ -38,18 +41,16 @@ public function __construct( private readonly StyleInterface $io, ) {} - public function addTask( - string $softwareName, - ): void { - $this->useMock and $softwareName = 'rr'; - $this->taskManager->addTask(function () use ($softwareName): void { + public function addTask(DownloadConfig $action): void + { + $this->taskManager->addTask(function () use ($action): void { // Find Software - $software = $this->softwareCollection->findSoftware($softwareName) ?? throw new \RuntimeException( + $software = $this->softwareCollection->findSoftware($action->software) ?? throw new \RuntimeException( 'Software not found.', ); // Create a Download task - $task = $this->prepareDownloadTask($software); + $task = $this->prepareDownloadTask($software, $action); // Extract files ($task->handler)()->then($this->prepareExtractTask($software)); @@ -61,7 +62,7 @@ public function run(): void $this->taskManager->await(); } - private function prepareDownloadTask(Software $software): DownloadTask + private function prepareDownloadTask(Software $software, DownloadConfig $action): DownloadTask { return $this->useMock ? new DownloadTask( @@ -74,7 +75,7 @@ private function prepareDownloadTask(Software $software): DownloadTask ), ), ) - : $this->downloader->download($software, static fn() => null); + : $this->downloader->download($software, $action, static fn() => null); } /** @@ -86,6 +87,7 @@ private function prepareExtractTask(Software $software): \Closure $fileInfo = $downloadResult->file; $archive = $this->archiveFactory->create($fileInfo); $extractor = $archive->extract(); + $this->logger->info('Extracting %s', $fileInfo->getFilename()); while ($extractor->valid()) { $file = $extractor->current(); diff --git a/src/Module/Common/Architecture.php b/src/Module/Common/Architecture.php index c38b9f1..54a0a5e 100644 --- a/src/Module/Common/Architecture.php +++ b/src/Module/Common/Architecture.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -19,7 +19,7 @@ enum Architecture: string implements Factoriable private const ERROR_UNKNOWN_ARCH = 'Current architecture `%s` may not be supported.'; - public static function create(BuildInput $config): self + public static function create(Build $config): self { return self::tryFrom((string) $config->arch) ?? self::fromGlobals(); } diff --git a/src/Module/Common/Config/Action/Download.php b/src/Module/Common/Config/Action/Download.php new file mode 100644 index 0000000..7c616d7 --- /dev/null +++ b/src/Module/Common/Config/Action/Download.php @@ -0,0 +1,31 @@ +software = $software; + return $action; + } +} diff --git a/src/Module/Common/Config/Actions.php b/src/Module/Common/Config/Actions.php new file mode 100644 index 0000000..ec6daad --- /dev/null +++ b/src/Module/Common/Config/Actions.php @@ -0,0 +1,18 @@ + */ + #[XPathEmbedList('/dload/actions/download', Download::class)] + public array $downloads = []; +} diff --git a/src/Module/Common/Config/SoftwareRegistry.php b/src/Module/Common/Config/CustomSoftwareRegistry.php similarity index 53% rename from src/Module/Common/Config/SoftwareRegistry.php rename to src/Module/Common/Config/CustomSoftwareRegistry.php index be299ce..e18b264 100644 --- a/src/Module/Common/Config/SoftwareRegistry.php +++ b/src/Module/Common/Config/CustomSoftwareRegistry.php @@ -4,12 +4,16 @@ namespace Internal\DLoad\Module\Common\Config; +use Internal\DLoad\Module\Common\Internal\Attribute\XPath; use Internal\DLoad\Module\Common\Internal\Attribute\XPathEmbedList; -final class SoftwareRegistry +final class CustomSoftwareRegistry { + #[XPath('/dload/registry/@overwrite')] + public bool $overwrite = false; + /** - * @var Embed\Software[] + * @var \Internal\DLoad\Module\Common\Config\Embed\Software[] */ #[XPathEmbedList('/dload/registry/software', Embed\Software::class)] public array $software = []; diff --git a/src/Module/Common/Config/DownloaderConfig.php b/src/Module/Common/Config/Downloader.php similarity index 50% rename from src/Module/Common/Config/DownloaderConfig.php rename to src/Module/Common/Config/Downloader.php index f9e9f86..855ca8b 100644 --- a/src/Module/Common/Config/DownloaderConfig.php +++ b/src/Module/Common/Config/Downloader.php @@ -4,7 +4,10 @@ namespace Internal\DLoad\Module\Common\Config; -final class DownloaderConfig +use Internal\DLoad\Module\Common\Internal\Attribute\XPath; + +final class Downloader { + #[XPath('/dload/@temp-dir')] public ?string $tmpDir = null; } diff --git a/src/Module/Common/Config/Embed/File.php b/src/Module/Common/Config/Embed/File.php index ede6d45..3f4f60d 100644 --- a/src/Module/Common/Config/Embed/File.php +++ b/src/Module/Common/Config/Embed/File.php @@ -6,6 +6,12 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; +/** + * @psalm-type FileArray = array{ + * rename?: non-empty-string, + * pattern?: non-empty-string + * } + */ final class File { /** @@ -16,4 +22,16 @@ final class File #[XPath('@pattern')] public string $pattern = '/^.*$/'; + + /** + * @param FileArray $fileArray + */ + public static function fromArray(mixed $fileArray): self + { + $self = new self(); + $self->rename = $fileArray['rename'] ?? null; + $self->pattern = $fileArray['pattern'] ?? '/^.*$/'; + + return $self; + } } diff --git a/src/Module/Common/Config/Embed/Repository.php b/src/Module/Common/Config/Embed/Repository.php index 8742fe4..e5e8ae4 100644 --- a/src/Module/Common/Config/Embed/Repository.php +++ b/src/Module/Common/Config/Embed/Repository.php @@ -6,6 +6,13 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; +/** + * @psalm-type RepositoryArray = array{ + * type: non-empty-string, + * uri: non-empty-string, + * asset-pattern?: non-empty-string + * } + */ final class Repository { #[XPath('@type')] @@ -16,4 +23,17 @@ final class Repository #[XPath('@asset-pattern')] public string $assetPattern = '/^.*$/'; + + /** + * @param RepositoryArray $repositoryArray + */ + public static function fromArray(mixed $repositoryArray): self + { + $self = new self(); + $self->type = $repositoryArray['type'] ?? 'github'; + $self->uri = $repositoryArray['uri']; + $self->assetPattern = $repositoryArray['asset-pattern'] ?? '/^.*$/'; + + return $self; + } } diff --git a/src/Module/Common/Config/Embed/Software.php b/src/Module/Common/Config/Embed/Software.php index c0894bb..42f336c 100644 --- a/src/Module/Common/Config/Embed/Software.php +++ b/src/Module/Common/Config/Embed/Software.php @@ -7,6 +7,18 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; use Internal\DLoad\Module\Common\Internal\Attribute\XPathEmbedList; +/** + * @psalm-import-type RepositoryArray from Repository + * @psalm-import-type FileArray from File + * @psalm-type SoftwareArray = array{ + * name: non-empty-string, + * alias?: non-empty-string, + * homepage?: non-empty-string, + * description?: non-empty-string, + * repositories?: list, + * files?: list + * } + */ final class Software { /** @@ -22,17 +34,42 @@ final class Software #[XPath('@alias')] public ?string $alias = null; + #[XPath('@homepage')] + public ?string $homepage = null; + #[XPath('@description')] public string $description = ''; - /** @var list */ + /** @var File */ #[XPathEmbedList('repository', Repository::class)] public array $repositories = []; - /** @var list */ + /** @var File */ #[XPathEmbedList('file', File::class)] public array $files = []; + /** + * @param SoftwareArray $softwareArray + */ + public static function fromArray(mixed $softwareArray): self + { + $self = new self(); + $self->name = $softwareArray['name']; + $self->alias = $softwareArray['alias'] ?? null; + $self->homepage = $softwareArray['homepage'] ?? null; + $self->description = $softwareArray['description'] ?? ''; + $self->repositories = \array_map( + static fn(array $repositoryArray): Repository => Repository::fromArray($repositoryArray), + $softwareArray['repositories'] ?? [], + ); + $self->files = \array_map( + static fn(array $fileArray): File => File::fromArray($fileArray), + $softwareArray['files'] ?? [], + ); + + return $self; + } + /** * @return non-empty-string */ diff --git a/src/Module/Common/Config/GitHubConfig.php b/src/Module/Common/Config/GitHub.php similarity index 90% rename from src/Module/Common/Config/GitHubConfig.php rename to src/Module/Common/Config/GitHub.php index d123322..34706c8 100644 --- a/src/Module/Common/Config/GitHubConfig.php +++ b/src/Module/Common/Config/GitHub.php @@ -9,7 +9,7 @@ /** * @internal */ -final class GitHubConfig +final class GitHub { #[Env('GITHUB_TOKEN')] public ?string $token = null; diff --git a/src/Module/Common/Config/BuildInput.php b/src/Module/Common/Input/Build.php similarity index 91% rename from src/Module/Common/Config/BuildInput.php rename to src/Module/Common/Input/Build.php index e43aaa1..41bc720 100644 --- a/src/Module/Common/Config/BuildInput.php +++ b/src/Module/Common/Input/Build.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Internal\DLoad\Module\Common\Config; +namespace Internal\DLoad\Module\Common\Input; use Internal\DLoad\Module\Common\Architecture; use Internal\DLoad\Module\Common\Internal\Attribute\InputOption; @@ -12,7 +12,7 @@ /** * @internal */ -final class BuildInput +final class Build { /** * Use {@see Architecture} to get final value. diff --git a/src/Module/Common/Config/Destination.php b/src/Module/Common/Input/Destination.php similarity index 81% rename from src/Module/Common/Config/Destination.php rename to src/Module/Common/Input/Destination.php index c1e2aed..ad0dc09 100644 --- a/src/Module/Common/Config/Destination.php +++ b/src/Module/Common/Input/Destination.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Internal\DLoad\Module\Common\Config; +namespace Internal\DLoad\Module\Common\Input; use Internal\DLoad\Module\Common\Internal\Attribute\InputOption; diff --git a/src/Module/Common/Internal/Injection/ConfigLoader.php b/src/Module/Common/Internal/Injection/ConfigLoader.php index 203a5b1..8aaacc0 100644 --- a/src/Module/Common/Internal/Injection/ConfigLoader.php +++ b/src/Module/Common/Internal/Injection/ConfigLoader.php @@ -126,8 +126,12 @@ private function getXPath(XPath $attribute): mixed private function getXPathEmbeddedList(XPathEmbedList $attribute): array { + if ($this->xml === null) { + return []; + } + $result = []; - $value = $this->xml?->xpath($attribute->path); + $value = $this->xml->xpath($attribute->path); \is_array($value) or throw new \Exception(\sprintf('Invalid XPath `%s`', $attribute->path)); foreach ($value as $xml) { diff --git a/src/Module/Common/OperatingSystem.php b/src/Module/Common/OperatingSystem.php index d610665..6a2c2b4 100644 --- a/src/Module/Common/OperatingSystem.php +++ b/src/Module/Common/OperatingSystem.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -22,7 +22,7 @@ enum OperatingSystem: string implements Factoriable private const ERROR_UNKNOWN_OS = 'Current OS `%s` may not be supported'; - public static function create(BuildInput $config): static + public static function create(Build $config): static { return self::tryFrom((string) $config->os) ?? self::fromGlobals(); } diff --git a/src/Module/Common/Stability.php b/src/Module/Common/Stability.php index fb15e60..d211558 100644 --- a/src/Module/Common/Stability.php +++ b/src/Module/Common/Stability.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -20,7 +20,7 @@ enum Stability: string implements Factoriable case Alpha = 'alpha'; case Dev = 'dev'; - public static function create(BuildInput $config): static + public static function create(Build $config): static { return self::tryFrom((string) $config->stability) ?? self::fromGlobals(); } diff --git a/src/Module/Downloader/Downloader.php b/src/Module/Downloader/Downloader.php index c5e37b4..0f4bf81 100644 --- a/src/Module/Downloader/Downloader.php +++ b/src/Module/Downloader/Downloader.php @@ -6,7 +6,8 @@ use Internal\DLoad\Module\Archive\ArchiveFactory; use Internal\DLoad\Module\Common\Architecture; -use Internal\DLoad\Module\Common\Config\DownloaderConfig; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; +use Internal\DLoad\Module\Common\Config\Downloader as DownloaderConfig; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Common\OperatingSystem; use Internal\DLoad\Module\Common\Stability; @@ -44,11 +45,13 @@ public function __construct( */ public function download( Software $software, + DownloadConfig $actionConfig, \Closure $onProgress, ): DownloadTask { $context = new DownloadContext( software: $software, onProgress: $onProgress, + actionConfig: $actionConfig, ); $repositories = $software->repositories; @@ -91,10 +94,21 @@ public function download( private function processRepository(RepositoryInterface $repository, DownloadContext $context): \Closure { return function () use ($repository, $context): ReleaseInterface { + $this->logger->info( + 'Loading releases from `%s` repository %s', + $context->repoConfig->type, + $repository->getName(), + ); + + $releasesCollection = $repository->getReleases() + ->minimumStability($this->stability); + + // Filter by version if specified + $context->actionConfig->version === null or $releasesCollection = $releasesCollection + ->satisfies($context->actionConfig->version); + /** @var ReleaseInterface[] $releases */ - $releases = $repository->getReleases() - ->minimumStability($this->stability) - ->sortByVersion()->toArray(); + $releases = $releasesCollection->sortByVersion()->toArray(); $this->logger->debug('%d releases found.', \count($releases)); @@ -102,12 +116,13 @@ private function processRepository(RepositoryInterface $repository, DownloadCont $releases === [] and throw new \RuntimeException('No relevant release found.'); $context->release = \array_shift($releases); - $this->logger->debug('Trying to load release `%s`', $context->release->getName()); + $this->logger->info('Loading release `%s`', $context->release->getName()); try { await(coroutine($this->processRelease($context))); return $context->release; } catch (\Throwable $e) { + $this->logger->error('%s', $e->getMessage()); $this->logger->exception($e); goto process_release; } @@ -154,7 +169,7 @@ private function processAsset(DownloadContext $context): \Closure $temp = $this->getTempDirectory() . DIRECTORY_SEPARATOR . $context->asset->getName(); $file = new \SplFileObject($temp, 'wb+'); - $this->logger->debug('Downloading into ' . $temp); + $this->logger->info('Downloading into %s', $temp); await(coroutine( (static function () use ($context, $file): void { diff --git a/src/Module/Downloader/Internal/DownloadContext.php b/src/Module/Downloader/Internal/DownloadContext.php index 50b81bc..2ef349e 100644 --- a/src/Module/Downloader/Internal/DownloadContext.php +++ b/src/Module/Downloader/Internal/DownloadContext.php @@ -4,6 +4,7 @@ namespace Internal\DLoad\Module\Downloader\Internal; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; use Internal\DLoad\Module\Common\Config\Embed\Repository; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Downloader\Progress; @@ -31,5 +32,6 @@ final class DownloadContext public function __construct( public readonly Software $software, public readonly \Closure $onProgress, + public readonly DownloadConfig $actionConfig, ) {} } diff --git a/src/Module/Downloader/SoftwareCollection.php b/src/Module/Downloader/SoftwareCollection.php index aad13ef..bce7f0f 100644 --- a/src/Module/Downloader/SoftwareCollection.php +++ b/src/Module/Downloader/SoftwareCollection.php @@ -4,8 +4,9 @@ namespace Internal\DLoad\Module\Downloader; +use Internal\DLoad\Info; +use Internal\DLoad\Module\Common\Config\CustomSoftwareRegistry; use Internal\DLoad\Module\Common\Config\Embed\Software; -use Internal\DLoad\Module\Common\Config\SoftwareRegistry; use IteratorAggregate; /** @@ -13,19 +14,21 @@ */ final class SoftwareCollection implements \IteratorAggregate, \Countable { - public function __construct( - private readonly SoftwareRegistry $softwareRegistry, - ) {} + /** @var array */ + private array $registry = []; - public function findSoftware(string $name): ?Software + public function __construct(CustomSoftwareRegistry $softwareRegistry) { - foreach ($this->softwareRegistry->software as $software) { - if ($software->getId() === $name) { - return $software; - } + foreach ($softwareRegistry->software as $software) { + $this->registry[$software->getId()] = $software; } - return null; + $softwareRegistry->overwrite or $this->loadDefaultRegistry(); + } + + public function findSoftware(string $name): ?Software + { + return $this->registry[$name] ?? null; } /** @@ -33,7 +36,7 @@ public function findSoftware(string $name): ?Software */ public function getIterator(): \Traversable { - yield from $this->softwareRegistry->software; + yield from $this->registry; } /** @@ -41,6 +44,21 @@ public function getIterator(): \Traversable */ public function count(): int { - return \count($this->softwareRegistry->software); + return \count($this->registry); + } + + private function loadDefaultRegistry(): void + { + $json = \json_decode( + \file_get_contents(Info::ROOT_DIR . '/resources/software.json'), + true, + 16, + JSON_THROW_ON_ERROR, + ); + + foreach ($json as $softwareArray) { + $software = Software::fromArray($softwareArray); + $this->registry[$software->getId()] ??= $software; + } } } diff --git a/src/Module/Downloader/TaskManager.php b/src/Module/Downloader/TaskManager.php index 6959eda..687e7ec 100644 --- a/src/Module/Downloader/TaskManager.php +++ b/src/Module/Downloader/TaskManager.php @@ -41,6 +41,7 @@ public function getProcessor(): \Generator yield $task->resume(); } catch (\Throwable $e) { + $this->logger->error($e->getMessage()); $this->logger->exception($e); unset($this->tasks[$key]); yield $e; @@ -55,7 +56,7 @@ public function await(): void $processor = $this->getProcessor(); $processor->current(); while ($processor->valid()) { - $processor->send(null); + $processor->next(); } } } diff --git a/src/Module/Repository/Collection/ReleasesCollection.php b/src/Module/Repository/Collection/ReleasesCollection.php index c5814a8..7191d88 100644 --- a/src/Module/Repository/Collection/ReleasesCollection.php +++ b/src/Module/Repository/Collection/ReleasesCollection.php @@ -16,33 +16,21 @@ final class ReleasesCollection extends Collection { /** - * @param non-empty-string ...$constraints + * @param non-empty-string $constraint * @return $this */ - public function satisfies(string ...$constraints): self + public function satisfies(string $constraint): self { - $result = $this; - - foreach ($this->constraints($constraints) as $constraint) { - $result = $result->filter(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); - } - - return $result; + return $this->filter(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); } /** - * @param string ...$constraints + * @param non-empty-string $constraint * @return $this */ - public function notSatisfies(string ...$constraints): self + public function notSatisfies(string $constraint): self { - $result = $this; - - foreach ($this->constraints($constraints) as $constraint) { - $result = $result->except(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); - } - - return $result; + return $this->except(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); } /** @@ -99,23 +87,6 @@ public function minimumStability(Stability $stability): self ); } - /** - * @param array $constraints - * @return array - */ - private function constraints(array $constraints): array - { - $result = []; - - foreach ($constraints as $constraint) { - foreach (\explode('|', $constraint) as $expression) { - $result[] = $expression; - } - } - - return \array_unique(\array_filter(\array_map('\\trim', $result))); - } - /** * @return non-empty-string */ diff --git a/src/Module/Repository/Internal/GitHub/Factory.php b/src/Module/Repository/Internal/GitHub/Factory.php index 712426e..3a9ea3e 100644 --- a/src/Module/Repository/Internal/GitHub/Factory.php +++ b/src/Module/Repository/Internal/GitHub/Factory.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Repository\Internal\GitHub; -use Internal\DLoad\Module\Common\Config\GitHubConfig; +use Internal\DLoad\Module\Common\Config\GitHub as GitHubConfig; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; diff --git a/src/Service/Logger.php b/src/Service/Logger.php index 8fc50e3..6dd0a79 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -36,7 +36,7 @@ public function status(string $sender, string $message, string|int|float|bool .. public function info(string $message, string|int|float|bool ...$values): void { - $this->echo("\033[32m" . \sprintf($message, ...$values) . "\033[0m\n", !$this->verbose); + $this->echo("\033[32m" . \sprintf($message, ...$values) . "\033[0m\n", false); } public function debug(string $message, string|int|float|bool ...$values): void