mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-05 15:55:39 +08:00
Refactor lock component to a single class (#773)
This commit is contained in:
227
src/SPC/store/LockFile.php
Normal file
227
src/SPC/store/LockFile.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\store;
|
||||
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
|
||||
class LockFile
|
||||
{
|
||||
private const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json';
|
||||
|
||||
private static ?array $lock_file_content = null;
|
||||
|
||||
/**
|
||||
* Get a lock entry by its name.
|
||||
*
|
||||
* @param string $lock_name Lock name to retrieve
|
||||
* @return null|array{
|
||||
* source_type: string,
|
||||
* filename: ?string,
|
||||
* dirname: ?string,
|
||||
* move_path: ?string,
|
||||
* lock_as: int,
|
||||
* hash: string
|
||||
* } Returns the lock entry as an associative array if it exists, or null if it does not
|
||||
*/
|
||||
public static function get(string $lock_name): ?array
|
||||
{
|
||||
self::init();
|
||||
|
||||
// Return the specific lock entry if it exists, otherwise return an empty array
|
||||
$result = self::$lock_file_content[$lock_name] ?? null;
|
||||
|
||||
// Add old `dir` compatibility
|
||||
if (($result['source_type'] ?? null) === 'dir') {
|
||||
logger()->warning("Lock entry for '{$lock_name}' has 'source_type' set to 'dir', which is deprecated. Please re-download your dependencies.");
|
||||
$result['source_type'] = SPC_SOURCE_GIT;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a lock file exists for a given lock name.
|
||||
*
|
||||
* @param string $lock_name Lock name to check
|
||||
*/
|
||||
public static function isLockFileExists(string $lock_name): bool
|
||||
{
|
||||
return match (self::get($lock_name)['source_type'] ?? null) {
|
||||
SPC_SOURCE_ARCHIVE => file_exists(DOWNLOAD_PATH . '/' . (self::get($lock_name)['filename'] ?? '.never-exist-file')),
|
||||
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => is_dir(DOWNLOAD_PATH . '/' . (self::get($lock_name)['dirname'] ?? '.never-exist-dir')),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a lock entry into the lock file.
|
||||
*
|
||||
* @param string $lock_name Lock name to set or remove
|
||||
* @param null|array $lock_content lock content to set, or null to remove the lock entry
|
||||
* @throws FileSystemException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function put(string $lock_name, ?array $lock_content): void
|
||||
{
|
||||
self::init();
|
||||
|
||||
$data = self::$lock_file_content;
|
||||
if ($lock_content === null && isset($data[$lock_name])) {
|
||||
self::removeLockFileIfExists($data[$lock_name]);
|
||||
unset($data[$lock_name]);
|
||||
} else {
|
||||
$data[$lock_name] = $lock_content;
|
||||
}
|
||||
|
||||
// Write the updated lock data back to the file
|
||||
file_put_contents(self::LOCK_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path of a lock file or directory based on the lock options.
|
||||
*
|
||||
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||
* @return string the absolute path to the lock file or directory
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function getLockFullPath(array $lock_options): string
|
||||
{
|
||||
return match ($lock_options['source_type']) {
|
||||
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
|
||||
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
|
||||
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
|
||||
};
|
||||
}
|
||||
|
||||
public static function getExtractPath(string $lock_name, string $default_path): ?string
|
||||
{
|
||||
$lock = self::get($lock_name);
|
||||
if ($lock === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If move_path is set, use it; otherwise, use the default extract directory
|
||||
if (isset($lock['move_path'])) {
|
||||
if (FileSystem::isRelativePath($lock['move_path'])) {
|
||||
// If move_path is relative, prepend the default extract directory
|
||||
return match ($lock['lock_as']) {
|
||||
SPC_DOWNLOAD_SOURCE, SPC_DOWNLOAD_PRE_BUILT => FileSystem::convertPath(SOURCE_PATH . '/' . $lock['move_path']),
|
||||
SPC_DOWNLOAD_PACKAGE => FileSystem::convertPath(PKG_ROOT_PATH . '/' . $lock['move_path']),
|
||||
default => throw new WrongUsageException("Unknown lock type: {$lock['lock_as']}"),
|
||||
};
|
||||
}
|
||||
return FileSystem::convertPath($lock['move_path']);
|
||||
}
|
||||
return FileSystem::convertPath($default_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of the lock source based on the lock options.
|
||||
*
|
||||
* @param array $lock_options Lock options
|
||||
* @return string Hash of the lock source
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function getLockSourceHash(array $lock_options): string
|
||||
{
|
||||
$result = match ($lock_options['source_type']) {
|
||||
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
|
||||
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
|
||||
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
|
||||
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
|
||||
};
|
||||
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
|
||||
}
|
||||
return $result ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $lock_options Lock options
|
||||
* @param string $destination Target directory
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function putLockSourceHash(array $lock_options, string $destination): void
|
||||
{
|
||||
$hash = LockFile::getLockSourceHash($lock_options);
|
||||
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
|
||||
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
|
||||
return;
|
||||
}
|
||||
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to lock source with hash.
|
||||
*
|
||||
* @param string $name Source name
|
||||
* @param array{
|
||||
* source_type: string,
|
||||
* dirname: ?string,
|
||||
* filename: ?string,
|
||||
* move_path: ?string,
|
||||
* lock_as: int
|
||||
* } $data Source data
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function lockSource(string $name, array $data): void
|
||||
{
|
||||
// calculate hash
|
||||
$hash = LockFile::getLockSourceHash($data);
|
||||
$data['hash'] = $hash;
|
||||
self::put($name, $data);
|
||||
}
|
||||
|
||||
private static function init(): void
|
||||
{
|
||||
if (self::$lock_file_content === null) {
|
||||
// Initialize the lock file content if it hasn't been loaded yet
|
||||
if (!file_exists(self::LOCK_FILE)) {
|
||||
logger()->debug('Lock file does not exist: ' . self::LOCK_FILE . ', initializing empty lock file.');
|
||||
self::$lock_file_content = [];
|
||||
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
} else {
|
||||
$file_content = file_get_contents(self::LOCK_FILE);
|
||||
self::$lock_file_content = json_decode($file_content, true);
|
||||
if (self::$lock_file_content === null) {
|
||||
throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the lock file or directory if it exists.
|
||||
*
|
||||
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
private static function removeLockFileIfExists(array $lock_options): void
|
||||
{
|
||||
if ($lock_options['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||
$path = self::getLockFullPath($lock_options);
|
||||
if (file_exists($path)) {
|
||||
logger()->info('Removing file ' . $path);
|
||||
unlink($path);
|
||||
} else {
|
||||
logger()->debug("Lock file [{$lock_options['filename']}] not found, skip removing file.");
|
||||
}
|
||||
} else {
|
||||
$path = self::getLockFullPath($lock_options);
|
||||
if (is_dir($path)) {
|
||||
logger()->info('Removing directory ' . $path);
|
||||
FileSystem::removeDir($path);
|
||||
} else {
|
||||
logger()->debug("Lock directory [{$lock_options['dirname']}] not found, skip removing directory.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user