cloudflare_stream-8.x-1.0/src/Tus/UploadClient.php

src/Tus/UploadClient.php
<?php

namespace Drupal\cloudflare_stream\Tus;

use Drupal\Core\Utility\Error;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Log\LoggerInterface;

/**
 * A TUS client tailored to our use.
 *
 * The most commonly used TUS client in packagist is too opinionated to work
 * with Cloudflare Streams as it insists on doing the work of requesting the
 * upload location.
 */
class UploadClient {

  const CHUNK_SIZE = 52428800;

  /**
   * May be substituted for testing via ::setHttpClient().
   */
  protected Client $httpClient;

  public function __construct(
    protected readonly string $baseUrl,
    protected readonly string $filePath,
    protected readonly string $apiToken,
    protected readonly LoggerInterface $logger,
    protected readonly int $chunkSize = self::CHUNK_SIZE,
  ) {
    $this->httpClient = new Client();
  }

  /**
   * Override the client for testing.
   *
   * @param \GuzzleHttp\Client $httpClient
   *   A client.
   */
  public function setHttpClient(Client $httpClient): void {
    $this->httpClient = $httpClient;
  }

  /**
   * Performs the TUS upload.
   */
  public function upload() {
    if (file_exists($this->filePath) === FALSE) {
      throw new \RuntimeException("File $this->filePath does not exist.");
    }

    // Initialize variables.
    $info = @stat($this->filePath);
    $size = $info['size'];
    $offset = 0;
    $handle = fopen($this->filePath, 'r');
    if ($handle === FALSE) {
      throw new \RuntimeException("Unable to read $this->filePath");
    }

    // Now upload the file.
    $remainder = $size;
    do {
      $bytes = min($remainder, $this->chunkSize);

      // Move file pointer to the offset.
      $position = fseek($handle, $offset);
      if ($position === -1) {
        throw new \RuntimeException("Unable to move internal file pointer to requested location.");
      }

      // Read the chunk.
      $data = fread($handle, $bytes);
      if ($data === FALSE) {
        throw new \RuntimeException("Cannot read the file data.");
      }

      // Send the chunk.
      try {
        $response = $this->httpClient->patch($this->baseUrl, [
          'body' => $data,
          'headers' => $this->calculateHeaders($bytes, $offset),
        ]);
        if ($response->getStatusCode() === 204) {
          $expectedOffset = $offset + $bytes;
          $reportedOffsetHeaders = $response->getHeader('Upload-Offset');
          $reportedOffset = (int) reset($reportedOffsetHeaders);
          if ($reportedOffset !== $expectedOffset) {
            throw new \RuntimeException("Cloudflare reported offset of $reportedOffset but $expectedOffset was expected.");
          }
          // Everything checks out: advance the offset.
          $offset = $expectedOffset;
        }
      }
      catch (GuzzleException $e) {
        Error::logException($this->logger, $e, 'Recovering from Guzzle error in \Drupal\cloudflare_stream\Tus\UploadClient::upload()');
      }
      $remainder = $size - $offset;

    } while ($remainder > 0);

    // Close the file.
    fclose($handle);
  }

  /**
   * Prepare the headers.
   *
   * @param int $bytes
   *   The size of the payload.
   * @param int $offset
   *   The current offset.
   *
   * @return mixed[]
   *   The headers.
   */
  protected function calculateHeaders(int $bytes, int $offset): array {
    return [
      'Tus-Resumable' => '1.0.0',
      'Content-Type' => 'application/offset+octet-stream',
      'Content-Length' => $bytes,
      'Upload-Offset' => $offset,
      'Authorization' => 'Bearer ' . $this->apiToken,
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc