mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-18 21:04:52 +08:00
216 lines
8.6 KiB
PHP
216 lines
8.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace SPC\store;
|
|
|
|
use SPC\exception\SPCInternalException;
|
|
use SPC\exception\WrongUsageException;
|
|
|
|
class LockFile
|
|
{
|
|
public 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
|
|
*/
|
|
public static function put(string $lock_name, ?array $lock_content): void
|
|
{
|
|
self::init();
|
|
|
|
if ($lock_content === null && isset(self::$lock_file_content[$lock_name])) {
|
|
self::removeLockFileIfExists(self::$lock_file_content[$lock_name]);
|
|
unset(self::$lock_file_content[$lock_name]);
|
|
} else {
|
|
self::$lock_file_content[$lock_name] = $lock_content;
|
|
}
|
|
|
|
// Write the updated lock data back to the file
|
|
FileSystem::createDir(dirname(self::LOCK_FILE));
|
|
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, 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
|
|
*/
|
|
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
|
|
*/
|
|
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 SPCInternalException("Unknown source type: {$lock_options['source_type']}"),
|
|
};
|
|
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
|
throw new SPCInternalException("Failed to get hash for source: {$lock_options['source_type']}");
|
|
}
|
|
return $result ?: '';
|
|
}
|
|
|
|
/**
|
|
* @param array $lock_options Lock options
|
|
* @param string $destination Target directory
|
|
*/
|
|
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
|
|
*/
|
|
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 SPCInternalException('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'
|
|
*/
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|