295 lines
8.1 KiB
PHP
295 lines
8.1 KiB
PHP
<?php
|
|
namespace filesystem\adapter;
|
|
|
|
use League\Flysystem\Config;
|
|
use League\Flysystem\FilesystemAdapter;
|
|
use League\Flysystem\FileAttributes;
|
|
use League\Flysystem\UnableToCopyFile;
|
|
use League\Flysystem\UnableToDeleteFile;
|
|
use League\Flysystem\UnableToMoveFile;
|
|
use League\Flysystem\UnableToReadFile;
|
|
use League\Flysystem\UnableToRetrieveMetadata;
|
|
use League\Flysystem\UnableToSetVisibility;
|
|
use League\Flysystem\UnableToWriteFile;
|
|
use Qiniu\Auth;
|
|
use Qiniu\Storage\UploadManager;
|
|
use Qiniu\Storage\BucketManager;
|
|
|
|
class QiniuAdapter implements FilesystemAdapter
|
|
{
|
|
protected ?Auth $authMgr;
|
|
protected ?UploadManager $uploadMgr;
|
|
protected ?BucketManager $bucketMgr;
|
|
|
|
public function __construct(protected string $access_key, protected string $secret_key, protected string $bucket, protected string $base_url, protected string $path)
|
|
{
|
|
}
|
|
|
|
private function getAuthMgr(): Auth
|
|
{
|
|
return $this->authMgr ?? new Auth($this->access_key, $this->secret_key);
|
|
}
|
|
|
|
private function getUploadMgr(): UploadManager
|
|
{
|
|
return $this->uploadMgr ?? new UploadManager();
|
|
}
|
|
|
|
private function getBucketMgr(): BucketManager
|
|
{
|
|
return $this->bucketMgr ?? new BucketManager($this->authMgr);
|
|
}
|
|
|
|
private function getPathPrefix(): string
|
|
{
|
|
$path = ltrim($this->path, '\\/');
|
|
if ($path !== '' && !str_ends_with($path, '/')) {
|
|
$path = $path . '/';
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
private function applyPathPrefix(string $path): string
|
|
{
|
|
$path = ltrim($path, '\\/');
|
|
return $this->getPathPrefix() . $path;
|
|
}
|
|
|
|
private static function parseUrl($url): array
|
|
{
|
|
$result = [];
|
|
|
|
// Build arrays of values we need to decode before parsing
|
|
$entities = [
|
|
'%21',
|
|
'%2A',
|
|
'%27',
|
|
'%28',
|
|
'%29',
|
|
'%3B',
|
|
'%3A',
|
|
'%40',
|
|
'%26',
|
|
'%3D',
|
|
'%24',
|
|
'%2C',
|
|
'%2F',
|
|
'%3F',
|
|
'%23',
|
|
'%5B',
|
|
'%5D',
|
|
'%5C'
|
|
];
|
|
$replacements = ['!', '*', "'", '(', ')', ';', ':', '@', '&', '=', '$', ',', '/', '?', '#', '[', ']', '/'];
|
|
|
|
// Create encoded URL with special URL characters decoded so it can be parsed
|
|
// All other characters will be encoded
|
|
$encodedURL = str_replace($entities, $replacements, urlencode($url));
|
|
|
|
// Parse the encoded URL
|
|
$encodedParts = parse_url($encodedURL);
|
|
|
|
// Now, decode each value of the resulting array
|
|
if ($encodedParts) {
|
|
foreach ($encodedParts as $key => $value) {
|
|
$result[$key] = urldecode(str_replace($replacements, $entities, $value));
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function normalizeHost($domain): string
|
|
{
|
|
if (0 !== stripos($domain, 'https://') && 0 !== stripos($domain, 'http://')) {
|
|
$domain = "http://{$domain}";
|
|
}
|
|
|
|
return rtrim($domain, '/') . '/';
|
|
}
|
|
|
|
private function getUrl(string $path): string
|
|
{
|
|
$segments = $this->parseUrl($path);
|
|
$query = empty($segments['query']) ? '' : '?' . $segments['query'];
|
|
|
|
return $this->normalizeHost($this->base_url) . ltrim(implode('/', array_map('rawurlencode', explode('/', $segments['path']))), '/') . $query;
|
|
}
|
|
|
|
private function privateDownloadUrl(string $path, int $expires = 3600): string
|
|
{
|
|
return $this->getAuthMgr()->privateDownloadUrl($this->getUrl($path), $expires);
|
|
}
|
|
|
|
private function getMetadata($path): FileAttributes|array
|
|
{
|
|
$result = $this->getBucketMgr()->stat($this->bucket, $path);
|
|
$result[0]['key'] = $path;
|
|
|
|
return $this->normalizeFileInfo($result[0]);
|
|
}
|
|
|
|
private function normalizeFileInfo(array $stats): FileAttributes
|
|
{
|
|
return new FileAttributes(
|
|
$stats['key'],
|
|
$stats['fsize'] ?? null,
|
|
null,
|
|
isset($stats['putTime']) ? floor($stats['putTime'] / 10000000) : null,
|
|
$stats['mimeType'] ?? null
|
|
);
|
|
}
|
|
|
|
public function fileExists(string $path): bool
|
|
{
|
|
[, $error] = $this->getBucketMgr()->stat($this->bucket, $this->applyPathPrefix($path));
|
|
return is_null($error);
|
|
}
|
|
|
|
public function directoryExists(string $path): bool
|
|
{
|
|
return $this->fileExists($path);
|
|
}
|
|
|
|
public function write(string $path, string $contents, Config $config): void
|
|
{
|
|
$mime = $config->get('mime', 'application/octet-stream');
|
|
|
|
/**
|
|
* @var Error|null $error
|
|
*/
|
|
[, $error] = $this->getUploadMgr()->put(
|
|
$this->getAuthMgr()->uploadToken($this->bucket),
|
|
$this->applyPathPrefix($path),
|
|
$contents,
|
|
null,
|
|
$mime,
|
|
$path
|
|
);
|
|
|
|
if ($error) {
|
|
throw UnableToWriteFile::atLocation($path, $error->message());
|
|
}
|
|
}
|
|
|
|
public function writeStream(string $path, $resource, Config $config): void
|
|
{
|
|
$data = '';
|
|
|
|
while (!feof($resource)) {
|
|
$data .= fread($resource, 1024);
|
|
}
|
|
|
|
$this->write($path, $data, $config);
|
|
}
|
|
|
|
public function read(string $path): string
|
|
{
|
|
try {
|
|
$result = file_get_contents($this->privateDownloadUrl($path));
|
|
} catch (\Exception $th) {
|
|
throw UnableToReadFile::fromLocation($path);
|
|
}
|
|
|
|
if (false === $result) {
|
|
throw UnableToReadFile::fromLocation($path);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function readStream(string $path)
|
|
{
|
|
if (ini_get('allow_url_fopen')) {
|
|
if ($result = fopen($this->privateDownloadUrl($path), 'r')) {
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
throw UnableToReadFile::fromLocation($path);
|
|
}
|
|
|
|
public function delete(string $path): void
|
|
{
|
|
[, $error] = $this->getBucketMgr()->delete($this->bucket, $this->applyPathPrefix($path));
|
|
if (!is_null($error)) {
|
|
throw UnableToDeleteFile::atLocation($path);
|
|
}
|
|
}
|
|
|
|
public function deleteDirectory(string $path): void
|
|
{
|
|
$this->delete($path);
|
|
}
|
|
|
|
public function createDirectory(string $path, Config $config): void
|
|
{
|
|
}
|
|
|
|
public function setVisibility(string $path, string $visibility): void
|
|
{
|
|
throw UnableToSetVisibility::atLocation($path);
|
|
}
|
|
|
|
public function visibility(string $path): FileAttributes
|
|
{
|
|
throw UnableToRetrieveMetadata::visibility($path);
|
|
}
|
|
|
|
public function mimeType(string $path): FileAttributes
|
|
{
|
|
$meta = $this->getMetadata($path);
|
|
|
|
if ($meta->mimeType() === null) {
|
|
throw UnableToRetrieveMetadata::mimeType($path);
|
|
}
|
|
|
|
return $meta;
|
|
}
|
|
|
|
public function lastModified(string $path): FileAttributes
|
|
{
|
|
$meta = $this->getMetadata($path);
|
|
|
|
if ($meta->lastModified() === null) {
|
|
throw UnableToRetrieveMetadata::lastModified($path);
|
|
}
|
|
return $meta;
|
|
}
|
|
|
|
public function fileSize(string $path): FileAttributes
|
|
{
|
|
$meta = $this->getMetadata($path);
|
|
|
|
if ($meta->fileSize() === null) {
|
|
throw UnableToRetrieveMetadata::fileSize($path);
|
|
}
|
|
return $meta;
|
|
}
|
|
|
|
public function listContents(string $path, bool $deep): iterable
|
|
{
|
|
$result = $this->getBucketMgr()->listFiles($this->bucket, $path);
|
|
|
|
foreach ($result[0]['items'] ?? [] as $files) {
|
|
yield $this->normalizeFileInfo($files);
|
|
}
|
|
}
|
|
|
|
public function move(string $source, string $destination, Config $config): void
|
|
{
|
|
[, $error] = $this->getBucketMgr()->rename($this->bucket, $source, $destination);
|
|
if (!is_null($error)) {
|
|
throw UnableToMoveFile::fromLocationTo($source, $destination);
|
|
}
|
|
}
|
|
|
|
public function copy(string $source, string $destination, Config $config): void
|
|
{
|
|
[, $error] = $this->getBucketMgr()->copy($this->bucket, $source, $this->bucket, $destination);
|
|
if (!is_null($error)) {
|
|
throw UnableToCopyFile::fromLocationTo($source, $destination);
|
|
}
|
|
}
|
|
} |