config->localRoot . str_replace('/', DIRECTORY_SEPARATOR, $key); } private function ensureDirectory(string $fullPath): void { $directory = dirname($fullPath); if (! is_dir($directory) && ! mkdir($directory, 0777, true) && ! is_dir($directory)) { throw new FileStorageException("Unable to create directory: {$directory}"); } } public function upload(UploadedFile|string $source, string $key, array $options = []): string { $fullPath = $this->fullPath($key); $this->ensureDirectory($fullPath); if ($source instanceof UploadedFile) { if (! $source->isValid()) { throw new FileStorageException($source->getErrorString() ?: 'Invalid uploaded file'); } if ($source->hasMoved()) { throw new FileStorageException('Uploaded file has already been moved'); } $source->move(dirname($fullPath), basename($fullPath)); return $key; } if (! is_file($source)) { throw new FileStorageException("Source file not found: {$source}"); } if (! copy($source, $fullPath)) { throw new FileStorageException("Failed to copy file to {$fullPath}"); } return $key; } public function put(string $key, string $contents, array $options = []): string { $fullPath = $this->fullPath($key); $this->ensureDirectory($fullPath); if (file_put_contents($fullPath, $contents) === false) { throw new FileStorageException("Failed to write file: {$fullPath}"); } return $key; } public function exists(string $key): bool { return is_file($this->fullPath($key)); } public function delete(string $key): bool { $fullPath = $this->fullPath($key); if (! is_file($fullPath)) { return false; } return unlink($fullPath); } public function read(string $key): string { $fullPath = $this->fullPath($key); if (! is_file($fullPath)) { throw new FileStorageException("File not found: {$key}"); } $contents = file_get_contents($fullPath); if ($contents === false) { throw new FileStorageException("Unable to read file: {$key}"); } return $contents; } public function downloadToTemp(string $key): string { $fullPath = $this->fullPath($key); if (! is_file($fullPath)) { throw new FileStorageException("File not found: {$key}"); } return $fullPath; } public function streamDownload(string $key, ?string $downloadName = null): ResponseInterface { $fullPath = $this->fullPath($key); if (! is_file($fullPath)) { throw new FileStorageException("File not found: {$key}"); } return service('response')->download($fullPath, null)->setFileName($downloadName ?: basename($key)); } public function temporaryUrl(string $key, int $ttlSeconds = 900): string { if (! $this->exists($key)) { throw new FileStorageException("File not found: {$key}"); } return base_url(ltrim(str_replace('\\', '/', $key), '/')); } public function url(string $key): string { return $this->temporaryUrl($key); } public function copy(string $fromKey, string $toKey): bool { $fromPath = $this->fullPath($fromKey); $toPath = $this->fullPath($toKey); if (! is_file($fromPath)) { throw new FileStorageException("Source file not found: {$fromKey}"); } $this->ensureDirectory($toPath); return copy($fromPath, $toPath); } }