WindowsUpdateStrategy.php•4.94 kB
<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\Lib\BinaryUpdater\Strategy;
use Psr\Log\LoggerInterface;
use Spiral\Files\FilesInterface;
/**
* Windows-specific strategy for updating binary files.
* Creates a batch script that runs in the background after the current process exits.
*/
final readonly class WindowsUpdateStrategy implements UpdateStrategyInterface
{
public function __construct(
private FilesInterface $files,
private ?LoggerInterface $logger = null,
) {}
public function update(string $sourcePath, string $targetPath): bool
{
$this->logger?->info("Using Windows update strategy for binary: {$targetPath}");
// Create the update script
$this->logger?->info("Creating Windows batch script...");
$scriptPath = $this->createUpdateScript($sourcePath, $targetPath);
if ($scriptPath === null) {
$this->logger?->error("Failed to create Windows batch script");
return false;
}
$this->logger?->info("Created batch script at: {$scriptPath}");
// Run the script in the background
$command = \sprintf(
'start /b "" %s',
\escapeshellarg($scriptPath),
);
$this->logger?->info("Executing batch script in background with command: {$command}");
// Execute the command
$output = [];
$resultCode = 0;
\exec($command, $output, $resultCode);
$success = $resultCode === 0;
if ($success) {
$this->logger?->info("Successfully started background update process");
} else {
$this->logger?->error("Failed to start background update process, exit code: {$resultCode}");
}
return $success;
}
/**
* Create a temporary batch script that will perform the update.
*
* @return string|null Path to the created script, or null if creation failed
*/
private function createUpdateScript(string $sourcePath, string $targetPath): ?string
{
try {
$scriptPath = $this->files->tempFilename('.bat');
$this->logger?->info("Generated temporary script path: {$scriptPath}");
// Convert paths to Windows-style
$sourcePath = \str_replace('/', '\\', $sourcePath);
$targetPath = \str_replace('/', '\\', $targetPath);
$targetDir = \str_replace('/', '\\', \dirname($targetPath));
$scriptPathWin = \str_replace('/', '\\', $scriptPath);
$this->logger?->debug("Windows paths: source={$sourcePath}, target={$targetPath}, target_dir={$targetDir}");
$scriptContent = <<<BATCH
@echo off
rem Wait for parent process to exit
timeout /t 1 /nobreak > nul
rem Define paths
set SOURCE={$sourcePath}
set TARGET={$targetPath}
set TARGET_DIR={$targetDir}
echo Starting update process for %TARGET%
rem Create the target directory if it doesn't exist
if not exist "%TARGET_DIR%" mkdir "%TARGET_DIR%"
rem Try to update the file with multiple attempts
set MAX_ATTEMPTS=10
set ATTEMPT=1
set SUCCESS=0
:LOOP
if %ATTEMPT% gtr %MAX_ATTEMPTS% goto FAILED
if %SUCCESS% equ 1 goto SUCCESS
echo Attempt %ATTEMPT%: Trying to update %TARGET%
rem Try to copy the file
copy /Y "%SOURCE%" "%TARGET%" > nul 2>&1
if %ERRORLEVEL% equ 0 (
set SUCCESS=1
goto SUCCESS
) else (
echo Binary busy or permission denied, waiting 2 seconds...
timeout /t 2 /nobreak > nul
set /a ATTEMPT+=1
goto LOOP
)
:FAILED
echo Update failed after %MAX_ATTEMPTS% attempts.
goto CLEANUP
:SUCCESS
echo Update successful!
:CLEANUP
rem Clean up temporary files
del "%SOURCE%" > nul 2>&1
del "{$scriptPathWin}" > nul 2>&1
if %SUCCESS% equ 0 exit /b 1
exit /b 0
BATCH;
$this->logger?->debug("Writing batch script content to temporary file");
$this->files->write($scriptPath, $scriptContent);
return $scriptPath;
} catch (\Throwable $e) {
$this->logger?->error("Error creating batch script: {$e->getMessage()}");
return null;
}
}
}