yandexdisk-8.x-1.x-dev/src/StreamWrapper/YandexDiskStreamWrapper.php

src/StreamWrapper/YandexDiskStreamWrapper.php
<?php

namespace Drupal\yandexdisk\StreamWrapper;

use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\yandexdisk\YandexDiskException;

/**
 * Defines a Yandex.Disk (yandexdisk://) stream wrapper class.
 *
 * Provides support to work with users Disks via filesystem functions.
 */
class YandexDiskStreamWrapper implements StreamWrapperInterface {
  use StringTranslationTrait;

  /**
   * Indicates that stream was open for reading.
   */
  const OPEN_FOR_READ = 1;

  /**
   * Indicates that stream was open for writing.
   */
  const OPEN_FOR_WRITE = 2;

  /**
   * Stream context resource.
   *
   * @var resource
   */
  public $context;

  /**
   * A generic resource handle used for writing temporary files.
   *
   * @var resource
   */
  public $handle;

  /**
   * Instance URI.
   *
   * A stream is referenced as "scheme://target".
   *
   * @var string
   */
  protected $uri;

  /**
   * Yandex.Disk account instance.
   *
   * @var \Drupal\yandexdisk\YandexDiskApiWebdavHelper
   */
  protected $disk;

  /**
   * Username of Yandex.Disk account.
   *
   * @var string
   */
  public $user;

  /**
   * File path of the stream beginning with a slash.
   *
   * @var string
   */
  public $path;

  /**
   * Stream open mode.
   *
   * @var int
   */
  protected $openMode;

  /**
   * Pointer position in stream.
   *
   * @var int
   */
  protected $position;

  /**
   * Internal read buffer which prevents too many requests to Disk.
   *
   * @var string[]
   */
  protected $buffer;

  /**
   * List of directory contents for use with readdir().
   *
   * @var string[]
   */
  protected $directoryContents = [];

  /**
   * {@inheritdoc}
   */
  public static function getType() {
    return StreamWrapperInterface::HIDDEN;
  }

  /**
   * {@inheritdoc}
   */
  public function getName() {
    return $this->t('Yandex.Disk');
  }

  /**
   * {@inheritdoc}
   */
  public function getDescription() {
    return $this->t('Files served by Yandex.Disk cloud service.');
  }

  /**
   * Returns Disk class instance for current Disk account.
   *
   * @return \Drupal\yandexdisk\YandexDiskApiWebdavHelper|null
   *   Disk class instance.
   */
  public function disk() {
    if (!isset($this->disk) && isset($this->user)) {
      try {
        $this->disk = \Drupal::service('yandexdisk_manager')->getDisk($this->user);
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e, 'Request failed for @stream: @message', ['@stream' => $this->uri]);
      }
    }

    return $this->disk;
  }

  /**
   * {@inheritdoc}
   *
   * @return bool
   *   TRUE in case $uri is valid, FALSE otherwise.
   */
  public function setUri($uri) {
    // Characters "#", "?" and "&" are valid for YandexDisk paths. So
    // parse_url() is redundant here.
    $target = StreamWrapperManager::getTarget($uri);

    if ($target !== FALSE && $target !== '') {
      $this->uri = $uri;

      @list($this->user, $path) = explode('/', $target, 2);
      $this->path = '/' . $path;

      return TRUE;
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getUri() {
    return $this->uri;
  }

  /**
   * Opens file resource.
   *
   * @param string $uri
   *   The URL that was passed to the original function.
   * @param string $mode
   *   The mode used to open the file. Only the following modes supported:
   *   - 'r': Read-only. The method will fail if file does not exist.
   *   - 'w': Write-only.
   *   - 'c': Same as 'w' in this implementation.
   *   - 'x': Write-only. But the method will fail if file exists already.
   *   A '+' sign is not supported in mode.
   * @param int $options
   *   Additional flags set by the streams API. It can hold one or more of the
   *   following values OR'd together: STREAM_USE_PATH, STREAM_REPORT_ERRORS.
   * @param string $opened_url
   *   If the path is opened successfully, and STREAM_USE_PATH is set in
   *   options, opened_url should be set to the full path of the file/resource
   *   that was actually opened.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function stream_open($uri, $mode, $options, &$opened_url) {
    $success = FALSE;
    $error_reported = FALSE;

    // We don't support mutual read-n-write mode.
    if (@$mode[1] != '+' && $this->setUri($uri) && ($disk = $this->disk())) {
      $path = $this->path;

      try {
        // Check if the $mode is valid for the requested $uri.
        switch ($mode[0]) {
          case 'r':
            $success = $disk->isFile($path);
            if (!$success && $options & STREAM_REPORT_ERRORS) {
              trigger_error('No such file', E_USER_WARNING);
              $error_reported = TRUE;
            }
            break;

          case 'w':
          case 'c':
            try {
              $success = $disk->write($path, '');
            }
            catch (YandexDiskException $e) {
              watchdog_exception('yandexdisk', $e, 'Cannot create file @stream: @message', ['@stream' => $this->uri]);
              if ($options & STREAM_REPORT_ERRORS) {
                trigger_error('Cannot create file', E_USER_WARNING);
                $error_reported = TRUE;
              }
            }
            break;

          case 'x':
            $success = !$disk->isFile($path);
            if (!$success && $options & STREAM_REPORT_ERRORS) {
              trigger_error('File exists', E_USER_WARNING);
              $error_reported = TRUE;
            }
            if ($success) {
              try {
                $disk->write($path, '');
              }
              catch (YandexDiskException $e) {
                $success = FALSE;
                watchdog_exception('yandexdisk', $e, 'Cannot create file @stream: @message', ['@stream' => $this->uri]);
                if ($options & STREAM_REPORT_ERRORS) {
                  trigger_error('Cannot create file', E_USER_WARNING);
                  $error_reported = TRUE;
                }
              }
            }
            break;
        }
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e);
      }

      if ($success) {
        // Set pointer position and clear the buffer.
        $this->position = 0;
        $this->buffer = [];

        // Remember open mode.
        if ($mode[0] == 'r') {
          $this->openMode = self::OPEN_FOR_READ;
        }
        else {
          $this->openMode = self::OPEN_FOR_WRITE;
          $this->handle = tmpfile();
        }

        if ($options & STREAM_USE_PATH) {
          $opened_url = $uri;
        }
      }
    }

    if (!$success && !$error_reported && $options & STREAM_REPORT_ERRORS) {
      trigger_error('Cannot open stream', E_USER_WARNING);
    }

    return $success;
  }

  /**
   * Closes resource.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function stream_close() {
    return !($this->openMode & self::OPEN_FOR_WRITE) || fclose($this->handle);
  }

  /**
   * Advisory file locking.
   *
   * @param int $operation
   *   Locking operation.
   *
   * @return false
   *   Files locking is not supported.
   */
  public function stream_lock($operation) {
    return FALSE;
  }

  /**
   * Reads from stream.
   *
   * @param int $count
   *   How many bytes of data from the current position should be returned.
   *   (Usually 8Kb to add to buffer and then read from there).
   *
   * @return string|false
   *   Returns next part of file, or FALSE on failure.
   */
  public function stream_read($count) {
    if ($this->openMode & self::OPEN_FOR_READ) {
      try {
        if (!$this->buffer) {
          $buffer_size = \Drupal::config('yandexdisk.settings')->get('buffer_size');
          // With any result of reading from Disk the buffer array will contain
          // at least one empty string element.
          $this->buffer = str_split($this->disk->read($this->path, $this->position, $buffer_size), $count);
        }

        $data = array_shift($this->buffer);
        $this->position += strlen($data);
        return $data;
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e, 'Cannot read file @stream: @message', ['@stream' => $this->uri]);
      }
    }

    return FALSE;
  }

  /**
   * Writes to stream.
   *
   * @param string $data
   *   The data to write.
   *
   * @return int|false
   *   Number of bytes that were successfully stored.
   */
  public function stream_write($data) {
    if ($this->openMode & self::OPEN_FOR_WRITE) {
      fseek($this->handle, 0, SEEK_END);
      $length = (int) @fwrite($this->handle, $data);
      $this->position += $length;
      return $length;
    }

    return FALSE;
  }

  /**
   * Tests for end-of-file on a file pointer.
   *
   * @return bool
   *   Returns TRUE if the read position is at the end of the stream and if no
   *   more data is available to be read, or FALSE otherwise. Always returns
   *   TRUE if resource was open for writing.
   */
  public function stream_eof() {
    $properties = $this->disk->getProperties($this->path);
    return $this->position >= $properties['d:getcontentlength'];
  }

  /**
   * Seeks to specific location in a stream. Only for read mode.
   *
   * After calling this method this->position may differ from internal PHP's
   * buffer pointer. But it is OK as file stream behaves the same. An example is
   * in php.net docs.
   *
   * @param int $offset
   *   The stream offset to seek to.
   * @param int $whence
   *   Possible values: SEEK_SET, SEEK_CUR, SEEK_END.
   *
   * @return bool
   *   Returns TRUE if the position was updated, FALSE otherwise.
   *
   * @link http://php.net/manual/stream.streamwrapper.example-1.php
   */
  public function stream_seek($offset, $whence = SEEK_SET) {
    if ($this->openMode == self::OPEN_FOR_READ) {
      $properties = $this->disk->getProperties($this->path);

      switch ($whence) {
        case SEEK_SET:
          $new_position = $offset;
          break;

        case SEEK_CUR:
          $new_position = $offset + $this->position;
          break;

        case SEEK_END:
          $new_position = $offset + $properties['d:getcontentlength'];
          break;
      }

      if (isset($new_position)) {
        if ($new_position >= 0 && $new_position <= $properties['d:getcontentlength']) {
          $this->position = $new_position;
          $this->buffer = [];
          return TRUE;
        }
      }
    }

    return FALSE;
  }

  /**
   * Retrieves the current position of a stream.
   *
   * @return int
   *   The current position.
   */
  public function stream_tell() {
    return $this->position;
  }

  /**
   * Puts a file to its place on disk if resource was opened for writing.
   *
   * @return bool
   *   TRUE if the cached data was successfully stored (or if there was no data
   *   to store), or FALSE if the data could not be stored.
   */
  public function stream_flush() {
    if ($this->openMode & self::OPEN_FOR_WRITE && $this->position) {
      fseek($this->handle, 0);
      $data = fread($this->handle, $this->position);

      // Get the possible data type by analyzing file extension. Do not supply
      // the full uri as it will try to search the realpath.
      $content_type = \Drupal::service('file.mime_type.guesser')->guess($this->path);

      try {
        $this->disk->write($this->path, $data, $content_type);
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e, 'Cannot write file @stream: @message', ['@stream' => $this->uri]);
        return FALSE;
      }
    }

    return TRUE;
  }

  /**
   * Retrieves information about a resource.
   *
   * @return array|null
   *   Stat array on success.
   */
  public function stream_stat() {
    try {
      $properties = $this->disk->getProperties($this->path);

      $stat_props = [
        'dev',
        'ino',
        'nlink',
        'uid',
        'gid',
        'rdev',
        'atime',
        'ctime',
      ];
      $stat = array_fill_keys($stat_props, 0);
      $stat['blksize'] = $stat['blocks'] = -1;
      $stat['size'] = (int) @$properties['d:getcontentlength'];
      $stat['mtime'] = strtotime($properties['d:getlastmodified']);
      // File/dir mode.
      $stat['mode'] = isset($properties['d:collection']) ? 16749 : 33206;

      return $stat;
    }
    catch (YandexDiskException $e) {
      watchdog_exception('yandexdisk', $e);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function stream_cast($cast_as) {
    return $this->handle ? $this->handle : FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function stream_metadata($uri, $option, $value) {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function stream_set_option($option, $arg1, $arg2) {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function stream_truncate($new_size) {
    return ftruncate($this->handle, $new_size);
  }

  /**
   * Retrieves information about a file or directory.
   *
   * @param string $uri
   *   The file URL to stat.
   * @param int $flags
   *   Additional flags set by the streams API. It can hold one or more of the
   *   following values OR'd together: STREAM_URL_STAT_LINK,
   *   STREAM_URL_STAT_QUIET.
   *
   * @return array|false|null
   *   Stat array on success.
   */
  public function url_stat($uri, $flags) {
    if ($this->setUri($uri) && $this->disk()) {
      try {
        if ($this->disk->pathExists($this->path)) {
          return $this->stream_stat();
        }
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e);
        return FALSE;
      }
    }

    if (~$flags & STREAM_URL_STAT_QUIET) {
      trigger_error('No such file or directory', E_USER_WARNING);
    }

    return FALSE;
  }

  /**
   * Deletes a file or directory.
   *
   * @param string $uri
   *   The file URL which should be deleted.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function unlink($uri) {
    $success = FALSE;

    if ($this->setUri($uri) && $this->disk()) {
      try {
        $success = $this->disk->delete($this->path)->execute();
      }
      catch (YandexDiskException $e) {
        watchdog_exception('yandexdisk', $e);
      }
    }

    return $success;
  }

  /**
   * Renames a file or directory.
   *
   * @param string $from_uri
   *   The URL to the current file.
   * @param string $to_uri
   *   The URL which the $from_uri should be renamed to.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function rename($from_uri, $to_uri) {
    $success = FALSE;
    $destination = new self();

    try {
      if ($this->setUri($from_uri) && $destination->setUri($to_uri) && $this->disk()) {
        if ($destination->user == $this->user) {
          // Use native way to move resources within a Disk.
          $success = $this->disk->move($this->path, $destination->path)->execute();
        }
        elseif ($this->disk->pathExists($this->path) && $destination->disk()) {
          $success = \Drupal::service('yandexdisk_manager')->copyRecursive($this->disk, $this->path, $destination->disk, $destination->path)
            && $this->unlink($from_uri);
        }
      }
    }
    catch (YandexDiskException $e) {
      watchdog_exception('yandexdisk', $e);
    }

    return $success;
  }

  /**
   * Creates a directory.
   *
   * @param string $uri
   *   Directory which should be created.
   * @param int $mode
   *   The value passed to mkdir().
   * @param int $options
   *   A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE,
   *   STREAM_REPORT_ERRORS.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function mkdir($uri, $mode, $options) {
    $success = FALSE;

    try {
      if ($this->setUri($uri) && ($disk = $this->disk()) && !$disk->isDir($this->path)) {
        $success = $disk->mkdir($this->path, $options & STREAM_MKDIR_RECURSIVE);
      }
    }
    catch (YandexDiskException $e) {
      watchdog_exception('yandexdisk', $e);
    }

    if (!$success && $options & STREAM_REPORT_ERRORS) {
      trigger_error('Cannot create directory', E_USER_WARNING);
    }

    return $success;
  }

  /**
   * Removes a directory. Always recursive.
   *
   * @param string $uri
   *   The directory URL which should be removed.
   * @param int $options
   *   A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE,
   *   STREAM_REPORT_ERRORS.
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   *
   * @see YandexDiskStreamWrapper::unlink()
   */
  public function rmdir($uri, $options) {
    if ($this->unlink($uri)) {
      return TRUE;
    }
    elseif ($options & STREAM_REPORT_ERRORS) {
      trigger_error('Cannot remove directory', E_USER_WARNING);
    }

    return FALSE;
  }

  /**
   * Opens directory handle.
   *
   * @param string $uri
   *   Specifies the URL that was passed to opendir().
   * @param int $options
   *   Whether or not to enforce safe_mode (0x04).
   *
   * @return bool
   *   Returns TRUE on success or FALSE on failure.
   */
  public function dir_opendir($uri, $options) {
    try {
      if ($this->setUri($uri) && $this->disk()) {
        $this->directoryContents = $this->disk->scanDir($this->path);
        $this->position = 0;
        return TRUE;
      }
    }
    catch (YandexDiskException $e) {
      watchdog_exception('yandexdisk', $e);
    }

    return FALSE;
  }

  /**
   * Reads entry from directory handle.
   *
   * @return string|false
   *   The next filename, or FALSE if there is no next file.
   */
  public function dir_readdir() {
    if (isset($this->directoryContents[$this->position])) {
      return $this->directoryContents[$this->position++];
    }

    return FALSE;
  }

  /**
   * Rewinds directory handle.
   *
   * @return true
   *   Always returns TRUE.
   */
  public function dir_rewinddir() {
    $this->position = 0;
    return TRUE;
  }

  /**
   * Closes directory handle.
   *
   * @return true
   *   Always returns TRUE.
   */
  public function dir_closedir() {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   *
   * @return string|false
   *   Public URL.
   */
  public function getExternalUrl() {
    if ($this->uri && $this->disk()) {
      $path = StreamWrapperManager::getTarget($this->uri);
      $url = Url::fromRoute('yandexdisk.file_download', [], ['absolute' => TRUE, 'query' => ['file' => $path]])->toString();
    }

    return !empty($url) ? $url : FALSE;
  }

  /**
   * Returns no real paths.
   *
   * @return false
   *   Realpath is not supported.
   */
  public function realpath() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function dirname($uri = NULL) {
    if (!isset($uri) || $this->setUri($uri)) {
      // Remove erroneous leading or trailing, forward-slashes and backslashes.
      $target = trim($this->path, '\/');

      $dirname = dirname($target);
      if ($dirname == '.') {
        $dirname = '';
      }

      return 'yandexdisk://' . $this->user . '/' . $dirname;
    }

    return FALSE;
  }

}

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

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