<?php namespace App\Services\SongStorage; use App\Models\Song; use App\Models\User; use App\Services\FileScanner; use App\Values\SongStorageTypes; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; use League\Flysystem\Filesystem; use Spatie\Dropbox\Client; use Spatie\FlysystemDropbox\DropboxAdapter; final class DropboxStorage extends CloudStorage { public Filesystem $filesystem; private DropboxAdapter $adapter; public function __construct(protected FileScanner $scanner, private array $config) { parent::__construct($scanner); $client = new Client($this->maybeRefreshAccessToken()); $this->adapter = new DropboxAdapter($client); $this->filesystem = new Filesystem($this->adapter, ['case_sensitive' => false]); } public function storeUploadedFile(UploadedFile $file, User $uploader): Song { return DB::transaction(function () use ($file, $uploader): Song { $result = $this->scanUploadedFile($file, $uploader); $song = $this->scanner->getSong(); $key = $this->generateStorageKey($file->getClientOriginalName(), $uploader); $this->filesystem->write($key, File::get($result->path)); $song->update([ 'path' => "dropbox://$key", 'storage' => SongStorageTypes::DROPBOX, ]); File::delete($result->path); return $song; }); } private function maybeRefreshAccessToken(): string { $accessToken = Cache::get('dropbox_access_token'); if ($accessToken) { return $accessToken; } $response = Http::asForm() ->withBasicAuth($this->config['app_key'], $this->config['app_secret']) ->post('https://api.dropboxapi.com/oauth2/token', [ 'refresh_token' => $this->config['refresh_token'], 'grant_type' => 'refresh_token', ])->json(); Cache::put( 'dropbox_access_token', $response['access_token'], now()->addSeconds($response['expires_in'] - 60) // 60 seconds buffer ); return $response['access_token']; } public function getSongPresignedUrl(Song $song): string { return $this->adapter->getUrl($song->storage_metadata->getPath()); } public function supported(): bool { return SongStorageTypes::supported(SongStorageTypes::DROPBOX); } public function testSetup(): void { $this->filesystem->write('test.txt', 'Koel test file.'); $this->filesystem->delete('test.txt'); } }