config = $config ?? config('Storage'); $this->driver = $this->createDriver($this->config->driver); } public function driver(): FileStorageInterface { return $this->driver; } public function isS3(): bool { return $this->config->driver === 's3'; } public function resolveKey(string $module, string $subFolder, string $fileName): string { $module = trim($module, '/'); $subFolder = trim($subFolder, '/'); $fileName = ltrim(str_replace('\\', '/', $fileName), '/'); if ($subFolder === '') { return "uploads/{$module}/{$fileName}"; } return "uploads/{$module}/{$subFolder}/{$fileName}"; } public function upload(UploadedFile|string $source, string $key, array $options = []): string { $storedKey = $this->driver->upload($source, $key, $options); if ($this->config->retainLocal && $this->isS3()) { (new LocalStorageDriver($this->config))->upload($source, $key, $options); } return $storedKey; } public function uploadModuleFile( UploadedFile $file, string $module, string $subFolder = '', ?string $fileName = null ): string { $fileName = $fileName ?: (time() . '_' . $file->getRandomName()); $this->upload($file, $this->resolveKey($module, $subFolder, $fileName)); return $fileName; } public function put(string $key, string $contents, array $options = []): string { $storedKey = $this->driver->put($key, $contents, $options); if ($this->config->retainLocal && $this->isS3()) { (new LocalStorageDriver($this->config))->put($key, $contents, $options); } return $storedKey; } public function exists(string $module, string $subFolder, string $fileName): bool { return $this->driver->exists($this->resolveKey($module, $subFolder, $fileName)); } public function existsKey(string $key): bool { return $this->driver->exists($key); } public function delete(string $module, string $subFolder, string $fileName): bool { return $this->driver->delete($this->resolveKey($module, $subFolder, $fileName)); } public function deleteKey(string $key): bool { return $this->driver->delete($key); } public function read(string $module, string $subFolder, string $fileName): string { return $this->driver->read($this->resolveKey($module, $subFolder, $fileName)); } public function readKey(string $key): string { return $this->driver->read($key); } public function download(string $module, string $subFolder, string $fileName): ResponseInterface { return $this->driver->streamDownload( $this->resolveKey($module, $subFolder, $fileName), $fileName ); } public function downloadKey(string $key, ?string $downloadName = null): ResponseInterface { return $this->driver->streamDownload($key, $downloadName ?: basename($key)); } /** * Returns a local filesystem path suitable for pdftotext, Gemini, RAG, etc. * For S3, this downloads to a temp file first. */ public function getLocalPathForProcessing(string $module, string $subFolder, string $fileName): string { return $this->getLocalPathForKey($this->resolveKey($module, $subFolder, $fileName)); } public function getLocalPathForKey(string $key): string { if ($this->config->driver === 'local') { return rtrim($this->config->localRoot, '/\\') . DIRECTORY_SEPARATOR . ltrim(str_replace('/', DIRECTORY_SEPARATOR, $key), '/\\'); } return $this->driver->downloadToTemp($key); } public function getTemporaryUrl(string $module, string $subFolder, string $fileName, ?int $ttlSeconds = null): string { return $this->getTemporaryUrlForKey( $this->resolveKey($module, $subFolder, $fileName), $ttlSeconds ); } public function getTemporaryUrlForKey(string $key, ?int $ttlSeconds = null): string { return $this->driver->temporaryUrl($key, $ttlSeconds ?? $this->config->presignedTtl); } public function getUrl(string $module, string $subFolder, string $fileName): string { return $this->driver->url($this->resolveKey($module, $subFolder, $fileName)); } public function copy(string $fromModule, string $fromSubFolder, string $fromFileName, string $toModule, string $toSubFolder, string $toFileName): bool { return $this->driver->copy( $this->resolveKey($fromModule, $fromSubFolder, $fromFileName), $this->resolveKey($toModule, $toSubFolder, $toFileName) ); } private function createDriver(string $driver): FileStorageInterface { return match ($driver) { 's3' => new S3StorageDriver($this->config), default => new LocalStorageDriver($this->config), }; } }