Initial dev export (exclude uploads/runtime)
This commit is contained in:
231
core/services/Updater.php
Normal file
231
core/services/Updater.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Core\Services;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class Updater
|
||||
{
|
||||
private const CACHE_TTL_SECONDS = 21600;
|
||||
private const MANIFEST_URL = 'https://lab.codemunkeh.io/codemunkeh/AudioCore/raw/branch/main/update.json';
|
||||
|
||||
public static function currentVersion(): string
|
||||
{
|
||||
$data = require __DIR__ . '/../version.php';
|
||||
if (is_array($data) && !empty($data['version'])) {
|
||||
return (string)$data['version'];
|
||||
}
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
public static function getStatus(bool $force = false): array
|
||||
{
|
||||
$current = self::currentVersion();
|
||||
$manifestUrl = self::MANIFEST_URL;
|
||||
$channel = trim(Settings::get('update_channel', 'stable'));
|
||||
if ($channel === '') {
|
||||
$channel = 'stable';
|
||||
}
|
||||
|
||||
$cache = self::readCache();
|
||||
if (
|
||||
!$force
|
||||
&& is_array($cache)
|
||||
&& (string)($cache['manifest_url'] ?? '') === $manifestUrl
|
||||
&& (string)($cache['channel'] ?? '') === $channel
|
||||
&& ((int)($cache['fetched_at'] ?? 0) + self::CACHE_TTL_SECONDS) > time()
|
||||
) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
$status = [
|
||||
'ok' => false,
|
||||
'configured' => true,
|
||||
'current_version' => $current,
|
||||
'latest_version' => $current,
|
||||
'update_available' => false,
|
||||
'channel' => $channel,
|
||||
'manifest_url' => $manifestUrl,
|
||||
'error' => '',
|
||||
'checked_at' => gmdate('c'),
|
||||
'download_url' => '',
|
||||
'changelog_url' => '',
|
||||
'notes' => '',
|
||||
'fetched_at' => time(),
|
||||
];
|
||||
|
||||
try {
|
||||
$raw = self::fetchManifest($manifestUrl);
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
throw new \RuntimeException('Manifest JSON is invalid.');
|
||||
}
|
||||
|
||||
$release = self::pickReleaseForChannel($decoded, $channel);
|
||||
$latest = trim((string)($release['version'] ?? ''));
|
||||
if ($latest === '') {
|
||||
throw new \RuntimeException('Manifest does not include a version for selected channel.');
|
||||
}
|
||||
|
||||
$status['ok'] = true;
|
||||
$status['latest_version'] = $latest;
|
||||
$status['download_url'] = (string)($release['download_url'] ?? '');
|
||||
$status['changelog_url'] = (string)($release['changelog_url'] ?? '');
|
||||
$status['notes'] = (string)($release['notes'] ?? '');
|
||||
$status['update_available'] = version_compare($latest, $current, '>');
|
||||
} catch (Throwable $e) {
|
||||
$status['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
self::writeCache($status);
|
||||
return $status;
|
||||
}
|
||||
|
||||
private static function pickReleaseForChannel(array $manifest, string $channel): array
|
||||
{
|
||||
if (isset($manifest['channels']) && is_array($manifest['channels'])) {
|
||||
$channels = $manifest['channels'];
|
||||
if (isset($channels[$channel]) && is_array($channels[$channel])) {
|
||||
return $channels[$channel];
|
||||
}
|
||||
if (isset($channels['stable']) && is_array($channels['stable'])) {
|
||||
return $channels['stable'];
|
||||
}
|
||||
}
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
private static function cachePath(): string
|
||||
{
|
||||
return __DIR__ . '/../../storage/update_cache.json';
|
||||
}
|
||||
|
||||
private static function readCache(): ?array
|
||||
{
|
||||
try {
|
||||
$db = Database::get();
|
||||
if ($db) {
|
||||
$stmt = $db->query("
|
||||
SELECT checked_at, channel, manifest_url, current_version, latest_version,
|
||||
is_update_available, ok, error_text, payload_json
|
||||
FROM ac_update_checks
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
");
|
||||
$row = $stmt ? $stmt->fetch() : null;
|
||||
if (is_array($row)) {
|
||||
$payload = json_decode((string)($row['payload_json'] ?? ''), true);
|
||||
if (is_array($payload)) {
|
||||
$payload['channel'] = (string)($row['channel'] ?? ($payload['channel'] ?? 'stable'));
|
||||
$payload['manifest_url'] = (string)($row['manifest_url'] ?? ($payload['manifest_url'] ?? ''));
|
||||
$payload['current_version'] = (string)($row['current_version'] ?? ($payload['current_version'] ?? '0.0.0'));
|
||||
$payload['latest_version'] = (string)($row['latest_version'] ?? ($payload['latest_version'] ?? '0.0.0'));
|
||||
$payload['update_available'] = ((int)($row['is_update_available'] ?? 0) === 1);
|
||||
$payload['ok'] = ((int)($row['ok'] ?? 0) === 1);
|
||||
$payload['error'] = (string)($row['error_text'] ?? ($payload['error'] ?? ''));
|
||||
$checkedAt = (string)($row['checked_at'] ?? '');
|
||||
if ($checkedAt !== '') {
|
||||
$payload['checked_at'] = $checkedAt;
|
||||
$payload['fetched_at'] = strtotime($checkedAt) ?: ($payload['fetched_at'] ?? 0);
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
|
||||
$path = self::cachePath();
|
||||
if (!is_file($path)) {
|
||||
return null;
|
||||
}
|
||||
$raw = @file_get_contents($path);
|
||||
if ($raw === false) {
|
||||
return null;
|
||||
}
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
private static function writeCache(array $data): void
|
||||
{
|
||||
try {
|
||||
$db = Database::get();
|
||||
if ($db) {
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO ac_update_checks
|
||||
(checked_at, channel, manifest_url, current_version, latest_version, is_update_available, ok, error_text, payload_json)
|
||||
VALUES (NOW(), :channel, :manifest_url, :current_version, :latest_version, :is_update_available, :ok, :error_text, :payload_json)
|
||||
");
|
||||
$stmt->execute([
|
||||
':channel' => (string)($data['channel'] ?? 'stable'),
|
||||
':manifest_url' => (string)($data['manifest_url'] ?? ''),
|
||||
':current_version' => (string)($data['current_version'] ?? '0.0.0'),
|
||||
':latest_version' => (string)($data['latest_version'] ?? '0.0.0'),
|
||||
':is_update_available' => !empty($data['update_available']) ? 1 : 0,
|
||||
':ok' => !empty($data['ok']) ? 1 : 0,
|
||||
':error_text' => (string)($data['error'] ?? ''),
|
||||
':payload_json' => json_encode($data, JSON_UNESCAPED_SLASHES),
|
||||
]);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
|
||||
$path = self::cachePath();
|
||||
@file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
private static function fetchManifest(string $manifestUrl): string
|
||||
{
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'timeout' => 10,
|
||||
'header' => "User-Agent: AudioCore-Updater/1.0\r\nAccept: application/json\r\n",
|
||||
],
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$raw = @file_get_contents($manifestUrl, false, $ctx);
|
||||
if (is_string($raw) && $raw !== '') {
|
||||
return $raw;
|
||||
}
|
||||
|
||||
if (!function_exists('curl_init')) {
|
||||
throw new \RuntimeException('Unable to fetch manifest (file_get_contents failed and cURL is unavailable).');
|
||||
}
|
||||
|
||||
$ch = curl_init($manifestUrl);
|
||||
if ($ch === false) {
|
||||
throw new \RuntimeException('Unable to fetch manifest (failed to initialize cURL).');
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'User-Agent: AudioCore-Updater/1.0',
|
||||
'Accept: application/json',
|
||||
]);
|
||||
|
||||
$body = curl_exec($ch);
|
||||
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if (!is_string($body) || $body === '') {
|
||||
$detail = $err !== '' ? $err : ('HTTP ' . $httpCode);
|
||||
throw new \RuntimeException('Unable to fetch manifest via cURL: ' . $detail);
|
||||
}
|
||||
if ($httpCode >= 400) {
|
||||
throw new \RuntimeException('Manifest request failed with HTTP ' . $httpCode . '.');
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user