Files
Atomcms-edit/app/Services/Emulator/EmulatorSourceService.php
T
root f5666c104d refactor: integrate diagnostics into Commandocentrum and split EmulatorUpdateService
- Add DiagnosticRunner integration to Commandocentrum for system health display
- Refactor EmulatorUpdateService from 2524 lines to 395 lines (facade pattern)
- Extract EmulatorStatusService, EmulatorJarService, EmulatorSourceService
- Extract EmulatorBuildService, EmulatorSqlService, EmulatorBackupService
- Add shared EmulatorConfiguration trait for dependency injection
- Preserve backward compatibility on all public methods
2026-05-19 20:20:43 +02:00

274 lines
10 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Services\Emulator;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
class EmulatorSourceService
{
use EmulatorConfiguration;
public function __construct()
{
$this->loadConfiguration();
}
public function checkForUpdates(): ?array
{
$repo = $this->sourceRepo ?: $this->githubRepo;
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
if (! $repo) {
return null;
}
$localCheck = $this->checkLocalSourceUpdates();
if ($localCheck !== null) {
return $localCheck;
}
return $this->checkRemoteSourceUpdates($repo, $branch);
}
public function isSourceBuildAvailable(): bool
{
$hasRepo = ! in_array($this->sourceRepo, [null, '', '0'], true) || ! in_array($this->githubRepo, [null, '', '0'], true);
$hasPath = ! in_array($this->emulatorSourcePath, [null, '', '0'], true);
if (! $hasRepo || ! $hasPath) {
return false;
}
try {
$result = Process::timeout(5)->run("[ -d {$this->emulatorSourcePath} ] && echo 'exists' || echo 'not_exists'");
return trim($result->output()) === 'exists';
} catch (\Exception) {
return false;
}
}
private function checkLocalSourceUpdates(): ?array
{
$sourcePath = $this->emulatorSourcePath;
$existsCheck = Process::timeout(5)->run("[ -d {$sourcePath} ] && echo 'exists'");
if (! $existsCheck->successful() || trim($existsCheck->output()) !== 'exists') {
return $this->checkSourceByCloning();
}
try {
$gitCheck = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse --is-inside-work-tree 2>/dev/null");
if (! $gitCheck->successful()) {
return $this->checkSourceByCloning();
}
$currentCommitResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse HEAD");
if (! $currentCommitResult->successful()) {
return null;
}
$currentSha = trim($currentCommitResult->output());
$fetchResult = Process::timeout(30)->run("cd {$sourcePath} && git fetch origin 2>&1");
if ($fetchResult->failed()) {
Log::debug('[EmulatorSource] Git fetch failed, trying local check only');
}
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
$latestResult = Process::timeout(5)->run("cd {$sourcePath} && git rev-parse origin/{$branch} 2>/dev/null || git rev-parse HEAD");
if (! $latestResult->successful()) {
return null;
}
$latestSha = trim($latestResult->output());
$dateResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%ci' {$latestSha}");
$latestDate = null;
$latestTimestamp = time();
if ($dateResult->successful()) {
$latestDate = trim($dateResult->output(), "'");
if ($latestDate !== '' && $latestDate !== '0') {
$latestTimestamp = strtotime($latestDate);
}
}
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
$msgResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%s' {$latestSha}");
$latestMessage = $msgResult->successful() ? trim($msgResult->output()) : '';
$authorResult = Process::timeout(5)->run("cd {$sourcePath} && git log -1 --format='%an' {$latestSha}");
$latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : '';
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
$isUpdate = $storedSha !== null && $storedSha !== $latestSha;
if ($storedSha === null) {
$isUpdate = true;
}
return [
'has_update' => $isUpdate,
'latest_sha' => $latestSha,
'latest_date' => $latestDate,
'latest_timestamp' => $latestTimestamp,
'latest_message' => $latestMessage,
'latest_author' => $latestAuthor,
'stored_sha' => $storedSha,
'stored_date' => $storedDate,
'source' => 'local',
];
} catch (\Exception $e) {
Log::debug('[EmulatorSource] Local source check failed: ' . $e->getMessage());
return null;
}
}
private function checkSourceByCloning(): ?array
{
$repo = $this->sourceRepo ?: $this->githubRepo;
$branch = $this->sourceBranch ?: $this->githubBranch ?: 'main';
if (! $repo) {
return null;
}
$repo = preg_replace('#/tree/[^/]+/.*$#', '', $repo);
$repo = str_replace(['https://github.com/', 'http://github.com/'], '', $repo);
$repo = rtrim($repo, '/');
try {
$tempDir = '/tmp/emulator-source-check-' . Str::random(8);
$cloneResult = Process::timeout(120)->run(
"git clone --branch {$branch} --depth 1 https://github.com/{$repo}.git {$tempDir} 2>&1",
);
if ($cloneResult->failed()) {
return null;
}
$shaResult = Process::timeout(5)->run("cd {$tempDir} && git rev-parse HEAD");
$latestSha = $shaResult->successful() ? trim($shaResult->output()) : '';
$dateResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%ci'");
$latestDate = null;
$latestTimestamp = time();
if ($dateResult->successful()) {
$latestDate = trim($dateResult->output());
if ($latestDate !== '' && $latestDate !== '0') {
$latestTimestamp = strtotime($latestDate);
}
}
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
$msgResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%s'");
$latestMessage = $msgResult->successful() ? trim($msgResult->output()) : '';
$authorResult = Process::timeout(5)->run("cd {$tempDir} && git log -1 --format='%an'");
$latestAuthor = $authorResult->successful() ? trim($authorResult->output()) : '';
Process::timeout(10)->run("rm -rf {$tempDir}");
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
$isUpdate = $storedSha !== null && $storedSha !== $latestSha;
if ($storedSha === null) {
$isUpdate = true;
}
return [
'has_update' => $isUpdate,
'latest_sha' => $latestSha,
'latest_date' => $latestDate,
'latest_timestamp' => $latestTimestamp,
'latest_message' => $latestMessage,
'latest_author' => $latestAuthor,
'stored_sha' => $storedSha,
'stored_date' => $storedDate,
'source' => 'cloned',
];
} catch (\Exception $e) {
Log::debug('[EmulatorSource] Source clone check failed: ' . $e->getMessage());
return null;
}
}
private function checkRemoteSourceUpdates(string $repo, string $branch): ?array
{
try {
$response = Http::timeout(10)
->withHeaders([
'Accept' => 'application/vnd.github.v3+json',
'User-Agent' => 'AtomCMS-Emulator-Updater',
])
->get("https://api.github.com/repos/{$repo}/commits", [
'sha' => $branch,
'per_page' => 1,
]);
if (! $response->successful()) {
return null;
}
$commits = $response->json();
if (empty($commits) || ! isset($commits[0]['sha'])) {
return null;
}
$latestCommit = $commits[0];
$latestSha = $latestCommit['sha'];
$latestDate = $latestCommit['commit']['committer']['date'] ?? null;
$latestTimestamp = $latestDate ? strtotime((string) $latestDate) : time();
$this->persistSourceCommitInfo($latestSha, $latestTimestamp);
$installedDate = $this->settings->getOrDefault('emulator_jar_installed_date', null);
$storedSha = $this->settings->getOrDefault('emulator_source_commit', null);
$storedDate = $this->settings->getOrDefault('emulator_source_date', null);
if ($storedSha !== null && $storedSha === $latestSha) {
$isUpdate = false;
} elseif ($storedSha !== null && $storedSha !== $latestSha) {
$isUpdate = true;
} elseif ($installedDate !== null) {
$installedTimestamp = is_numeric($installedDate) ? (int) $installedDate : strtotime((string) $installedDate);
$isUpdate = $installedTimestamp < $latestTimestamp;
} else {
$isUpdate = false;
}
return [
'has_update' => $isUpdate,
'latest_sha' => $latestSha,
'latest_date' => $latestDate,
'latest_timestamp' => $latestTimestamp,
'latest_message' => $latestCommit['commit']['message'] ?? '',
'latest_author' => $latestCommit['commit']['author']['name'] ?? '',
'stored_sha' => $storedSha,
'stored_date' => $storedDate,
];
} catch (\Exception $e) {
Log::warning('[EmulatorSource] Could not check source updates via GitHub API', ['error' => $e->getMessage()]);
return null;
}
}
private function persistSourceCommitInfo(string $sha, int $timestamp): void
{
$this->settings->set('emulator_source_commit', $sha);
$this->settings->set('emulator_source_date', (string) $timestamp);
}
}