Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  /*
   3   * Copyright 2008 Google Inc.
   4   *
   5   * Licensed under the Apache License, Version 2.0 (the "License");
   6   * you may not use this file except in compliance with the License.
   7   * You may obtain a copy of the License at
   8   *
   9   *     http://www.apache.org/licenses/LICENSE-2.0
  10   *
  11   * Unless required by applicable law or agreed to in writing, software
  12   * distributed under the License is distributed on an "AS IS" BASIS,
  13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14   * See the License for the specific language governing permissions and
  15   * limitations under the License.
  16   */
  17  
  18  if (!class_exists('Google_Client')) {
  19    require_once dirname(__FILE__) . '/../autoload.php';
  20  }
  21  
  22  /*
  23   * This class implements a basic on disk storage. While that does
  24   * work quite well it's not the most elegant and scalable solution.
  25   * It will also get you into a heap of trouble when you try to run
  26   * this in a clustered environment.
  27   *
  28   * @author Chris Chabot <chabotc@google.com>
  29   */
  30  class Google_Cache_File extends Google_Cache_Abstract
  31  {
  32    const MAX_LOCK_RETRIES = 10;
  33    private $path;
  34    private $fh;
  35  
  36    /**
  37     * @var Google_Client the current client
  38     */
  39    private $client;
  40  
  41    public function __construct(Google_Client $client)
  42    {
  43      $this->client = $client;
  44      $this->path = $this->client->getClassConfig($this, 'directory');
  45    }
  46  
  47    public function get($key, $expiration = false)
  48    {
  49      $storageFile = $this->getCacheFile($key);
  50      $data = false;
  51  
  52      if (!file_exists($storageFile)) {
  53        $this->client->getLogger()->debug(
  54            'File cache miss',
  55            array('key' => $key, 'file' => $storageFile)
  56        );
  57        return false;
  58      }
  59  
  60      if ($expiration) {
  61        $mtime = filemtime($storageFile);
  62        if ((time() - $mtime) >= $expiration) {
  63          $this->client->getLogger()->debug(
  64              'File cache miss (expired)',
  65              array('key' => $key, 'file' => $storageFile)
  66          );
  67          $this->delete($key);
  68          return false;
  69        }
  70      }
  71  
  72      if ($this->acquireReadLock($storageFile)) {
  73        if (filesize($storageFile) > 0) {
  74          $data = fread($this->fh, filesize($storageFile));
  75          $data =  unserialize($data);
  76        } else {
  77          $this->client->getLogger()->debug(
  78              'Cache file was empty',
  79              array('file' => $storageFile)
  80          );
  81        }
  82        $this->unlock($storageFile);
  83      }
  84  
  85      $this->client->getLogger()->debug(
  86          'File cache hit',
  87          array('key' => $key, 'file' => $storageFile, 'var' => $data)
  88      );
  89  
  90      return $data;
  91    }
  92  
  93    public function set($key, $value)
  94    {
  95      $storageFile = $this->getWriteableCacheFile($key);
  96      if ($this->acquireWriteLock($storageFile)) {
  97        // We serialize the whole request object, since we don't only want the
  98        // responseContent but also the postBody used, headers, size, etc.
  99        $data = serialize($value);
 100        $result = fwrite($this->fh, $data);
 101        $this->unlock($storageFile);
 102  
 103        $this->client->getLogger()->debug(
 104            'File cache set',
 105            array('key' => $key, 'file' => $storageFile, 'var' => $value)
 106        );
 107      } else {
 108        $this->client->getLogger()->notice(
 109            'File cache set failed',
 110            array('key' => $key, 'file' => $storageFile)
 111        );
 112      }
 113    }
 114  
 115    public function delete($key)
 116    {
 117      $file = $this->getCacheFile($key);
 118      if (file_exists($file) && !unlink($file)) {
 119        $this->client->getLogger()->error(
 120            'File cache delete failed',
 121            array('key' => $key, 'file' => $file)
 122        );
 123        throw new Google_Cache_Exception("Cache file could not be deleted");
 124      }
 125  
 126      $this->client->getLogger()->debug(
 127          'File cache delete',
 128          array('key' => $key, 'file' => $file)
 129      );
 130    }
 131  
 132    private function getWriteableCacheFile($file)
 133    {
 134      return $this->getCacheFile($file, true);
 135    }
 136  
 137    private function getCacheFile($file, $forWrite = false)
 138    {
 139      return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
 140    }
 141  
 142    private function getCacheDir($file, $forWrite)
 143    {
 144      // use the first 2 characters of the hash as a directory prefix
 145      // this should prevent slowdowns due to huge directory listings
 146      // and thus give some basic amount of scalability
 147      $storageDir = $this->path . '/' . substr(md5($file), 0, 2);
 148      if ($forWrite && ! is_dir($storageDir)) {
 149        if (! mkdir($storageDir, 0700, true)) {
 150          $this->client->getLogger()->error(
 151              'File cache creation failed',
 152              array('dir' => $storageDir)
 153          );
 154          throw new Google_Cache_Exception("Could not create storage directory: $storageDir");
 155        }
 156      }
 157      return $storageDir;
 158    }
 159  
 160    private function acquireReadLock($storageFile)
 161    {
 162      return $this->acquireLock(LOCK_SH, $storageFile);
 163    }
 164  
 165    private function acquireWriteLock($storageFile)
 166    {
 167      $rc = $this->acquireLock(LOCK_EX, $storageFile);
 168      if (!$rc) {
 169        $this->client->getLogger()->notice(
 170            'File cache write lock failed',
 171            array('file' => $storageFile)
 172        );
 173        $this->delete($storageFile);
 174      }
 175      return $rc;
 176    }
 177  
 178    private function acquireLock($type, $storageFile)
 179    {
 180      $mode = $type == LOCK_EX ? "w" : "r";
 181      $this->fh = fopen($storageFile, $mode);
 182      if (!$this->fh) {
 183        $this->client->getLogger()->error(
 184            'Failed to open file during lock acquisition',
 185            array('file' => $storageFile)
 186        );
 187        return false;
 188      }
 189      if ($type == LOCK_EX) {
 190        chmod($storageFile, 0600);
 191      }
 192      $count = 0;
 193      while (!flock($this->fh, $type | LOCK_NB)) {
 194        // Sleep for 10ms.
 195        usleep(10000);
 196        if (++$count < self::MAX_LOCK_RETRIES) {
 197          return false;
 198        }
 199      }
 200      return true;
 201    }
 202  
 203    public function unlock($storageFile)
 204    {
 205      if ($this->fh) {
 206        flock($this->fh, LOCK_UN);
 207      }
 208    }
 209  }