|
16 | 16 | */ |
17 | 17 | class Downloader |
18 | 18 | { |
| 19 | + /** |
| 20 | + * Get latest version from PIE config (Packagist) |
| 21 | + * |
| 22 | + * @param string $name Source name |
| 23 | + * @param array $source Source meta info: [repo] |
| 24 | + * @return array<int, string> [url, filename] |
| 25 | + */ |
| 26 | + public static function getPIEInfo(string $name, array $source): array |
| 27 | + { |
| 28 | + $packagist_url = "https://repo.packagist.org/p2/{$source['repo']}.json"; |
| 29 | + logger()->debug("Fetching {$name} source from packagist index: {$packagist_url}"); |
| 30 | + $data = json_decode(self::curlExec( |
| 31 | + url: $packagist_url, |
| 32 | + retries: self::getRetryAttempts() |
| 33 | + ), true); |
| 34 | + if (!isset($data['packages'][$source['repo']]) || !is_array($data['packages'][$source['repo']])) { |
| 35 | + throw new DownloaderException("failed to find {$name} repo info from packagist"); |
| 36 | + } |
| 37 | + // get the first version |
| 38 | + $first = $data['packages'][$source['repo']][0] ?? []; |
| 39 | + // check 'type' => 'php-ext' or contains 'php-ext' key |
| 40 | + if (!isset($first['php-ext'])) { |
| 41 | + throw new DownloaderException("failed to find {$name} php-ext info from packagist, maybe not a php extension package"); |
| 42 | + } |
| 43 | + // get download link from dist |
| 44 | + $dist_url = $first['dist']['url'] ?? null; |
| 45 | + $dist_type = $first['dist']['type'] ?? null; |
| 46 | + if (!$dist_url || !$dist_type) { |
| 47 | + throw new DownloaderException("failed to find {$name} dist info from packagist"); |
| 48 | + } |
| 49 | + $name = str_replace('/', '_', $source['repo']); |
| 50 | + $version = $first['version'] ?? 'unknown'; |
| 51 | + // file name use: $name-$version.$dist_type |
| 52 | + return [$dist_url, "{$name}-{$version}.{$dist_type}"]; |
| 53 | + } |
| 54 | + |
19 | 55 | /** |
20 | 56 | * Get latest version from BitBucket tag |
21 | 57 | * |
@@ -317,84 +353,7 @@ public static function downloadPackage(string $name, ?array $pkg = null, bool $f |
317 | 353 | if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) { |
318 | 354 | return; |
319 | 355 | } |
320 | | - |
321 | | - try { |
322 | | - switch ($pkg['type']) { |
323 | | - case 'bitbuckettag': // BitBucket Tag |
324 | | - [$url, $filename] = self::getLatestBitbucketTag($name, $pkg); |
325 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE); |
326 | | - break; |
327 | | - case 'ghtar': // GitHub Release (tar) |
328 | | - [$url, $filename] = self::getLatestGithubTarball($name, $pkg); |
329 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, hooks: [[CurlHook::class, 'setupGithubToken']]); |
330 | | - break; |
331 | | - case 'ghtagtar': // GitHub Tag (tar) |
332 | | - [$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags'); |
333 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, hooks: [[CurlHook::class, 'setupGithubToken']]); |
334 | | - break; |
335 | | - case 'ghrel': // GitHub Release (uploaded) |
336 | | - [$url, $filename] = self::getLatestGithubRelease($name, $pkg); |
337 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]); |
338 | | - break; |
339 | | - case 'filelist': // Basic File List (regex based crawler) |
340 | | - [$url, $filename] = self::getFromFileList($name, $pkg); |
341 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE); |
342 | | - break; |
343 | | - case 'url': // Direct download URL |
344 | | - $url = $pkg['url']; |
345 | | - $filename = $pkg['filename'] ?? basename($pkg['url']); |
346 | | - self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE); |
347 | | - break; |
348 | | - case 'git': // Git repo |
349 | | - self::downloadGit( |
350 | | - $name, |
351 | | - $pkg['url'], |
352 | | - $pkg['rev'], |
353 | | - $pkg['submodules'] ?? null, |
354 | | - $pkg['extract'] ?? null, |
355 | | - self::getRetryAttempts(), |
356 | | - SPC_DOWNLOAD_PRE_BUILT |
357 | | - ); |
358 | | - break; |
359 | | - case 'local': |
360 | | - // Local directory, do nothing, just lock it |
361 | | - logger()->debug("Locking local source {$name}"); |
362 | | - LockFile::lockSource($name, [ |
363 | | - 'source_type' => SPC_SOURCE_LOCAL, |
364 | | - 'dirname' => $pkg['dirname'], |
365 | | - 'move_path' => $pkg['extract'] ?? null, |
366 | | - 'lock_as' => SPC_DOWNLOAD_PACKAGE, |
367 | | - ]); |
368 | | - break; |
369 | | - case 'custom': // Custom download method, like API-based download or other |
370 | | - $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'); |
371 | | - if (isset($pkg['func']) && is_callable($pkg['func'])) { |
372 | | - $pkg['name'] = $name; |
373 | | - $pkg['func']($force, $pkg, SPC_DOWNLOAD_PACKAGE); |
374 | | - break; |
375 | | - } |
376 | | - foreach ($classes as $class) { |
377 | | - if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) { |
378 | | - $cls = new $class(); |
379 | | - if (in_array($name, $cls->getSupportName())) { |
380 | | - (new $class())->fetch($name, $force, $pkg); |
381 | | - break; |
382 | | - } |
383 | | - } |
384 | | - } |
385 | | - break; |
386 | | - default: |
387 | | - throw new DownloaderException('unknown source type: ' . $pkg['type']); |
388 | | - } |
389 | | - } catch (\Throwable $e) { |
390 | | - // Because sometimes files downloaded through the command line are not automatically deleted after a failure. |
391 | | - // Here we need to manually delete the file if it is detected to exist. |
392 | | - if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) { |
393 | | - logger()->warning('Deleting download file: ' . $filename); |
394 | | - unlink(DOWNLOAD_PATH . '/' . $filename); |
395 | | - } |
396 | | - throw new DownloaderException('Download failed! ' . $e->getMessage()); |
397 | | - } |
| 356 | + self::downloadByType($pkg['type'], $name, $pkg, $force, SPC_DOWNLOAD_PACKAGE); |
398 | 357 | } |
399 | 358 |
|
400 | 359 | /** |
@@ -439,80 +398,7 @@ public static function downloadSource(string $name, ?array $source = null, bool |
439 | 398 | return; |
440 | 399 | } |
441 | 400 |
|
442 | | - try { |
443 | | - switch ($source['type']) { |
444 | | - case 'bitbuckettag': // BitBucket Tag |
445 | | - [$url, $filename] = self::getLatestBitbucketTag($name, $source); |
446 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as); |
447 | | - break; |
448 | | - case 'ghtar': // GitHub Release (tar) |
449 | | - [$url, $filename] = self::getLatestGithubTarball($name, $source); |
450 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); |
451 | | - break; |
452 | | - case 'ghtagtar': // GitHub Tag (tar) |
453 | | - [$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags'); |
454 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); |
455 | | - break; |
456 | | - case 'ghrel': // GitHub Release (uploaded) |
457 | | - [$url, $filename] = self::getLatestGithubRelease($name, $source); |
458 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]); |
459 | | - break; |
460 | | - case 'filelist': // Basic File List (regex based crawler) |
461 | | - [$url, $filename] = self::getFromFileList($name, $source); |
462 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as); |
463 | | - break; |
464 | | - case 'url': // Direct download URL |
465 | | - $url = $source['url']; |
466 | | - $filename = $source['filename'] ?? basename($source['url']); |
467 | | - self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as); |
468 | | - break; |
469 | | - case 'git': // Git repo |
470 | | - self::downloadGit( |
471 | | - $name, |
472 | | - $source['url'], |
473 | | - $source['rev'], |
474 | | - $source['submodules'] ?? null, |
475 | | - $source['path'] ?? null, |
476 | | - self::getRetryAttempts(), |
477 | | - $download_as |
478 | | - ); |
479 | | - break; |
480 | | - case 'local': |
481 | | - // Local directory, do nothing, just lock it |
482 | | - logger()->debug("Locking local source {$name}"); |
483 | | - LockFile::lockSource($name, [ |
484 | | - 'source_type' => SPC_SOURCE_LOCAL, |
485 | | - 'dirname' => $source['dirname'], |
486 | | - 'move_path' => $source['extract'] ?? null, |
487 | | - 'lock_as' => $download_as, |
488 | | - ]); |
489 | | - break; |
490 | | - case 'custom': // Custom download method, like API-based download or other |
491 | | - if (isset($source['func']) && is_callable($source['func'])) { |
492 | | - $source['name'] = $name; |
493 | | - $source['func']($force, $source, $download_as); |
494 | | - break; |
495 | | - } |
496 | | - $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'); |
497 | | - foreach ($classes as $class) { |
498 | | - if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { |
499 | | - (new $class())->fetch($force, $source, $download_as); |
500 | | - break; |
501 | | - } |
502 | | - } |
503 | | - break; |
504 | | - default: |
505 | | - throw new DownloaderException('unknown source type: ' . $source['type']); |
506 | | - } |
507 | | - } catch (\Throwable $e) { |
508 | | - // Because sometimes files downloaded through the command line are not automatically deleted after a failure. |
509 | | - // Here we need to manually delete the file if it is detected to exist. |
510 | | - if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) { |
511 | | - logger()->warning('Deleting download file: ' . $filename); |
512 | | - unlink(DOWNLOAD_PATH . '/' . $filename); |
513 | | - } |
514 | | - throw new DownloaderException('Download failed! ' . $e->getMessage()); |
515 | | - } |
| 401 | + self::downloadByType($source['type'], $name, $source, $force, $download_as); |
516 | 402 | } |
517 | 403 |
|
518 | 404 | /** |
@@ -713,4 +599,109 @@ private static function isAlreadyDownloaded(string $name, bool $force, int $down |
713 | 599 | } |
714 | 600 | return false; |
715 | 601 | } |
| 602 | + |
| 603 | + /** |
| 604 | + * Download by type. |
| 605 | + * |
| 606 | + * @param string $type Types |
| 607 | + * @param string $name Download item name |
| 608 | + * @param array{ |
| 609 | + * url?: string, |
| 610 | + * repo?: string, |
| 611 | + * rev?: string, |
| 612 | + * path?: string, |
| 613 | + * filename?: string, |
| 614 | + * dirname?: string, |
| 615 | + * match?: string, |
| 616 | + * prefer-stable?: bool, |
| 617 | + * extract?: string, |
| 618 | + * submodules?: array<string>, |
| 619 | + * provide-pre-built?: bool, |
| 620 | + * func?: ?callable, |
| 621 | + * license?: array |
| 622 | + * } $conf Download item config |
| 623 | + * @param bool $force Force download |
| 624 | + * @param int $download_as Lock source type |
| 625 | + */ |
| 626 | + private static function downloadByType(string $type, string $name, array $conf, bool $force, int $download_as): void |
| 627 | + { |
| 628 | + try { |
| 629 | + switch ($type) { |
| 630 | + case 'pie': // Packagist |
| 631 | + [$url, $filename] = self::getPIEInfo($name, $conf); |
| 632 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); |
| 633 | + break; |
| 634 | + case 'bitbuckettag': // BitBucket Tag |
| 635 | + [$url, $filename] = self::getLatestBitbucketTag($name, $conf); |
| 636 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); |
| 637 | + break; |
| 638 | + case 'ghtar': // GitHub Release (tar) |
| 639 | + [$url, $filename] = self::getLatestGithubTarball($name, $conf); |
| 640 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); |
| 641 | + break; |
| 642 | + case 'ghtagtar': // GitHub Tag (tar) |
| 643 | + [$url, $filename] = self::getLatestGithubTarball($name, $conf, 'tags'); |
| 644 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]); |
| 645 | + break; |
| 646 | + case 'ghrel': // GitHub Release (uploaded) |
| 647 | + [$url, $filename] = self::getLatestGithubRelease($name, $conf); |
| 648 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]); |
| 649 | + break; |
| 650 | + case 'filelist': // Basic File List (regex based crawler) |
| 651 | + [$url, $filename] = self::getFromFileList($name, $conf); |
| 652 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); |
| 653 | + break; |
| 654 | + case 'url': // Direct download URL |
| 655 | + $url = $conf['url']; |
| 656 | + $filename = $conf['filename'] ?? basename($conf['url']); |
| 657 | + self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); |
| 658 | + break; |
| 659 | + case 'git': // Git repo |
| 660 | + self::downloadGit($name, $conf['url'], $conf['rev'], $conf['submodules'] ?? null, $conf['path'] ?? $conf['extract'] ?? null, self::getRetryAttempts(), $download_as); |
| 661 | + break; |
| 662 | + case 'local': // Local directory, do nothing, just lock it |
| 663 | + LockFile::lockSource($name, [ |
| 664 | + 'source_type' => SPC_SOURCE_LOCAL, |
| 665 | + 'dirname' => $conf['dirname'], |
| 666 | + 'move_path' => $conf['path'] ?? $conf['extract'] ?? null, |
| 667 | + 'lock_as' => $download_as, |
| 668 | + ]); |
| 669 | + break; |
| 670 | + case 'custom': // Custom download method, like API-based download or other |
| 671 | + if (isset($conf['func']) && is_callable($conf['func'])) { |
| 672 | + $conf['name'] = $name; |
| 673 | + $conf['func']($force, $conf, $download_as); |
| 674 | + break; |
| 675 | + } |
| 676 | + $classes = [ |
| 677 | + ...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'), |
| 678 | + ...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'), |
| 679 | + ]; |
| 680 | + foreach ($classes as $class) { |
| 681 | + if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { |
| 682 | + (new $class())->fetch($force, $conf, $download_as); |
| 683 | + break; |
| 684 | + } |
| 685 | + if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) { |
| 686 | + $cls = new $class(); |
| 687 | + if (in_array($name, $cls->getSupportName())) { |
| 688 | + (new $class())->fetch($name, $force, $conf); |
| 689 | + break; |
| 690 | + } |
| 691 | + } |
| 692 | + } |
| 693 | + break; |
| 694 | + default: |
| 695 | + throw new DownloaderException("Unknown download type: {$type}"); |
| 696 | + } |
| 697 | + } catch (\Throwable $e) { |
| 698 | + // Because sometimes files downloaded through the command line are not automatically deleted after a failure. |
| 699 | + // Here we need to manually delete the file if it is detected to exist. |
| 700 | + if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) { |
| 701 | + logger()->warning("Deleting download file: {$filename}"); |
| 702 | + unlink(DOWNLOAD_PATH . '/' . $filename); |
| 703 | + } |
| 704 | + throw new DownloaderException("Download failed: {$e->getMessage()}"); |
| 705 | + } |
| 706 | + } |
716 | 707 | } |
0 commit comments