. */ declare(strict_types=1); namespace App\Services\System; use Psr\Log\LoggerInterface; use Shivas\VersioningBundle\Service\VersionManagerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Version\Version; /** * This class checks if a new version of Part-DB is available. */ class UpdateAvailableManager { private const API_URL = 'https://api.github.com/repos/Part-DB/Part-DB-server/releases/latest'; private const CACHE_KEY = 'uam_latest_version'; private const CACHE_TTL = 60 * 60 * 24 * 2; // 2 day public function __construct(private readonly HttpClientInterface $httpClient, private readonly CacheInterface $updateCache, private readonly VersionManagerInterface $versionManager, private readonly bool $check_for_updates, private readonly LoggerInterface $logger, #[Autowire(param: 'kernel.debug')] private readonly bool $is_dev_mode) { } /** * Gets the latest version of Part-DB as string (e.g. "1.2.3"). * This value is cached for 2 days. * @return string */ public function getLatestVersionString(): string { return $this->getLatestVersionInfo()['version']; } /** * Gets the latest version of Part-DB as Version object. */ public function getLatestVersion(): Version { return Version::fromString($this->getLatestVersionString()); } /** * Gets the URL to the latest version of Part-DB on GitHub. * @return string */ public function getLatestVersionUrl(): string { return $this->getLatestVersionInfo()['url']; } /** * Checks if a new version of Part-DB is available. This value is cached for 2 days. * @return bool */ public function isUpdateAvailable(): bool { //If we don't want to check for updates, we can return false if (!$this->check_for_updates) { return false; } $latestVersion = $this->getLatestVersion(); $currentVersion = $this->versionManager->getVersion(); return $latestVersion->isGreaterThan($currentVersion); } /** * Get the latest version info. The value is cached for 2 days. * @return array * @phpstan-return array{version: string, url: string} */ private function getLatestVersionInfo(): array { //If we don't want to check for updates, we can return dummy data if (!$this->check_for_updates) { return [ 'version' => '0.0.1', 'url' => 'update-checking-disabled' ]; } return $this->updateCache->get(self::CACHE_KEY, function (ItemInterface $item) { $item->expiresAfter(self::CACHE_TTL); try { $response = $this->httpClient->request('GET', self::API_URL); $result = $response->toArray(); $tag_name = $result['tag_name']; // Remove the leading 'v' from the tag name $version = substr($tag_name, 1); return [ 'version' => $version, 'url' => $result['html_url'], ]; } catch (\Exception $e) { //When we are in dev mode, throw the exception, otherwise just silently log it if ($this->is_dev_mode) { throw $e; } //In the case of an error, try it again after half of the cache time $item->expiresAfter(self::CACHE_TTL / 2); $this->logger->error('Checking for updates failed: ' . $e->getMessage()); return [ 'version' => '0.0.1', 'url' => 'update-checking-error' ]; } }); } }