292 lines
8.0 KiB
PHP
292 lines
8.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Filesystem;
|
|
|
|
use App\Framework\Async\FiberManager;
|
|
use App\Framework\Filesystem\Exceptions\DirectoryCreateException;
|
|
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
|
|
use App\Framework\Filesystem\Exceptions\FileReadException;
|
|
use App\Framework\Filesystem\Traits\InMemoryStreamableStorageTrait;
|
|
use App\Framework\Filesystem\ValueObjects\FileMetadata;
|
|
|
|
/**
|
|
* In-Memory-Implementierung des Storage-Interfaces für Tests
|
|
*/
|
|
final class InMemoryStorage implements Storage, StreamableStorage
|
|
{
|
|
use InMemoryStreamableStorageTrait;
|
|
|
|
/** @var array<string, string> */
|
|
private array $files = [];
|
|
|
|
/** @var array<string, bool> */
|
|
private array $directories = [];
|
|
|
|
/** @var array<string, int> */
|
|
private array $timestamps = [];
|
|
|
|
public readonly PermissionChecker $permissions;
|
|
|
|
public readonly FiberManager $fiberManager;
|
|
|
|
public function __construct()
|
|
{
|
|
// InMemoryStorage doesn't need real permission checks - create mock PathProvider
|
|
$mockPathProvider = new \App\Framework\Core\PathProvider('/tmp');
|
|
$this->permissions = new PermissionChecker($mockPathProvider);
|
|
$this->fiberManager = $this->createDefaultFiberManager();
|
|
}
|
|
|
|
private function createDefaultFiberManager(): FiberManager
|
|
{
|
|
// Create minimal dependencies for FiberManager
|
|
$clock = new \App\Framework\DateTime\SystemClock();
|
|
$timer = new \App\Framework\DateTime\SystemTimer($clock);
|
|
|
|
return new FiberManager($clock, $timer);
|
|
}
|
|
|
|
// Methods required by InMemoryStreamableStorageTrait
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function getFilesArray(): array
|
|
{
|
|
return $this->files;
|
|
}
|
|
|
|
protected function setFileContent(string $path, string $content): void
|
|
{
|
|
$this->files[$path] = $content;
|
|
$this->timestamps[$path] = time();
|
|
}
|
|
|
|
public function get(string $path): string
|
|
{
|
|
if (! isset($this->files[$path])) {
|
|
throw new FileNotFoundException($path);
|
|
}
|
|
|
|
return $this->files[$path];
|
|
}
|
|
|
|
public function put(string $path, string $content): void
|
|
{
|
|
$dir = dirname($path);
|
|
if (! isset($this->directories[$dir])) {
|
|
$this->createDirectory($dir);
|
|
}
|
|
|
|
$this->files[$path] = $content;
|
|
$this->timestamps[$path] = time();
|
|
}
|
|
|
|
public function exists(string $path): bool
|
|
{
|
|
return isset($this->files[$path]);
|
|
}
|
|
|
|
public function delete(string $path): void
|
|
{
|
|
if (! isset($this->files[$path])) {
|
|
throw new FileNotFoundException($path);
|
|
}
|
|
|
|
unset($this->files[$path]);
|
|
unset($this->timestamps[$path]);
|
|
}
|
|
|
|
public function copy(string $source, string $destination): void
|
|
{
|
|
if (! isset($this->files[$source])) {
|
|
throw new FileNotFoundException($source);
|
|
}
|
|
|
|
$dir = dirname($destination);
|
|
if (! isset($this->directories[$dir])) {
|
|
$this->createDirectory($dir);
|
|
}
|
|
|
|
$this->files[$destination] = $this->files[$source];
|
|
$this->timestamps[$destination] = time();
|
|
}
|
|
|
|
public function size(string $path): int
|
|
{
|
|
if (! isset($this->files[$path])) {
|
|
throw new FileNotFoundException($path);
|
|
}
|
|
|
|
return strlen($this->files[$path]);
|
|
}
|
|
|
|
public function lastModified(string $path): int
|
|
{
|
|
if (! isset($this->files[$path])) {
|
|
throw new FileNotFoundException($path);
|
|
}
|
|
|
|
return $this->timestamps[$path] ?? time();
|
|
}
|
|
|
|
public function listDirectory(string $directory): array
|
|
{
|
|
if (! isset($this->directories[$directory])) {
|
|
throw new DirectoryCreateException($directory);
|
|
}
|
|
|
|
$result = [];
|
|
$prefix = $directory . DIRECTORY_SEPARATOR;
|
|
$prefixLength = strlen($prefix);
|
|
|
|
// Dateien im Verzeichnis suchen
|
|
foreach (array_keys($this->files) as $path) {
|
|
if (str_starts_with($path, $prefix)) {
|
|
// Nur direkte Kinder, keine tieferen Ebenen
|
|
$relativePath = substr($path, $prefixLength);
|
|
if (! str_contains($relativePath, DIRECTORY_SEPARATOR)) {
|
|
$result[] = $path;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verzeichnisse im Verzeichnis suchen
|
|
foreach (array_keys($this->directories) as $path) {
|
|
if ($path !== $directory && strpos($path, $prefix) === 0) {
|
|
// Nur direkte Kinder
|
|
$relativePath = substr($path, $prefixLength);
|
|
if (! str_contains($relativePath, DIRECTORY_SEPARATOR)) {
|
|
$result[] = $path;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function createDirectory(string $path, int $permissions = 0755, bool $recursive = true): void
|
|
{
|
|
if (isset($this->directories[$path])) {
|
|
return; // Verzeichnis existiert bereits
|
|
}
|
|
|
|
if ($recursive) {
|
|
// Übergeordnete Verzeichnisse erstellen
|
|
$parent = dirname($path);
|
|
if ($parent !== '.' && $parent !== '/' && $parent !== $path) {
|
|
$this->createDirectory($parent, $permissions, true);
|
|
}
|
|
}
|
|
|
|
$this->directories[$path] = true;
|
|
}
|
|
|
|
/**
|
|
* Fügt eine Datei direkt zum In-Memory-Speicher hinzu (für Tests)
|
|
*/
|
|
public function addFile(string $path, string $content): void
|
|
{
|
|
$dir = dirname($path);
|
|
$this->createDirectory($dir);
|
|
$this->files[$path] = $content;
|
|
$this->timestamps[$path] = time();
|
|
}
|
|
|
|
/**
|
|
* Löscht alle Daten im Speicher (für Tests)
|
|
*/
|
|
public function clear(): void
|
|
{
|
|
$this->files = [];
|
|
$this->directories = [];
|
|
$this->timestamps = [];
|
|
}
|
|
|
|
/**
|
|
* Erstellt ein File-Objekt mit Lazy-Loading für teure Eigenschaften
|
|
*/
|
|
public function file(string $path): File
|
|
{
|
|
return FilesystemFactory::createFile($path, $this);
|
|
}
|
|
|
|
/**
|
|
* Erstellt ein Directory-Objekt mit Lazy-Loading für teure Eigenschaften
|
|
*/
|
|
public function directory(string $path): Directory
|
|
{
|
|
return FilesystemFactory::createDirectory($path, $this);
|
|
}
|
|
|
|
public function getMimeType(string $path): string
|
|
{
|
|
if (! isset($this->files[$path])) {
|
|
throw new FileNotFoundException($path);
|
|
}
|
|
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
if ($finfo === false) {
|
|
throw new FileReadException($path);
|
|
}
|
|
|
|
$mimeType = finfo_buffer($finfo, $this->files[$path]);
|
|
finfo_close($finfo);
|
|
|
|
return $mimeType ?: 'application/octet-stream';
|
|
}
|
|
|
|
public function isReadable(string $path): bool
|
|
{
|
|
return true; // Alle Dateien sind im In-Memory-Speicher lesbar
|
|
}
|
|
|
|
public function isWritable(string $path): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function batch(array $operations): array
|
|
{
|
|
return $this->fiberManager->batch($operations);
|
|
}
|
|
|
|
public function getMultiple(array $paths): array
|
|
{
|
|
$operations = [];
|
|
foreach ($paths as $path) {
|
|
$operations[$path] = fn () => $this->get($path);
|
|
}
|
|
|
|
return $this->batch($operations);
|
|
}
|
|
|
|
public function putMultiple(array $files): void
|
|
{
|
|
$operations = [];
|
|
foreach ($files as $path => $content) {
|
|
$operations[$path] = fn () => $this->put($path, $content);
|
|
}
|
|
$this->batch($operations);
|
|
}
|
|
|
|
public function getMetadataMultiple(array $paths): array
|
|
{
|
|
$operations = [];
|
|
foreach ($paths as $path) {
|
|
$operations[$path] = fn () => new FileMetadata(
|
|
path: $path,
|
|
size: $this->size($path),
|
|
lastModified: $this->lastModified($path),
|
|
mimeType: $this->getMimeType($path),
|
|
isReadable: $this->isReadable($path),
|
|
isWritable: $this->isWritable($path)
|
|
);
|
|
}
|
|
|
|
return $this->batch($operations);
|
|
}
|
|
}
|