/core/Google/Cache/File.php

  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. require_once realpath(dirname(__FILE__) . '/../../../autoload.php'); 
  19.  
  20. /** 
  21. * This class implements a basic on disk storage. While that does 
  22. * work quite well it's not the most elegant and scalable solution. 
  23. * It will also get you into a heap of trouble when you try to run 
  24. * this in a clustered environment. 
  25. * 
  26. * @author Chris Chabot <chabotc@google.com> 
  27. */ 
  28. class GoogleGAL_Cache_File extends GoogleGAL_Cache_Abstract 
  29. const MAX_LOCK_RETRIES = 10; 
  30. private $path; 
  31. private $fh; 
  32.  
  33. /** 
  34. * @var GoogleGAL_Client the current client 
  35. */ 
  36. private $client; 
  37.  
  38. public function __construct(GoogleGAL_Client $client) 
  39. $this->client = $client; 
  40. $this->path = $this->client->getClassConfig($this, 'directory'); 
  41.  
  42. public function get($key, $expiration = false) 
  43. $storageFile = $this->getCacheFile($key); 
  44. $data = false; 
  45.  
  46. if (!file_exists($storageFile)) { 
  47. $this->client->getLogger()->debug( 
  48. 'File cache miss',  
  49. array('key' => $key, 'file' => $storageFile) 
  50. ); 
  51. return false; 
  52.  
  53. if ($expiration) { 
  54. $mtime = filemtime($storageFile); 
  55. if ((time() - $mtime) >= $expiration) { 
  56. $this->client->getLogger()->debug( 
  57. 'File cache miss (expired)',  
  58. array('key' => $key, 'file' => $storageFile) 
  59. ); 
  60. $this->delete($key); 
  61. return false; 
  62.  
  63. if ($this->acquireReadLock($storageFile)) { 
  64. $data = fread($this->fh, filesize($storageFile)); 
  65. $data = unserialize($data); 
  66. $this->unlock($storageFile); 
  67.  
  68. $this->client->getLogger()->debug( 
  69. 'File cache hit',  
  70. array('key' => $key, 'file' => $storageFile, 'var' => $data) 
  71. ); 
  72.  
  73. return $data; 
  74.  
  75. public function set($key, $value) 
  76. $storageFile = $this->getWriteableCacheFile($key); 
  77. if ($this->acquireWriteLock($storageFile)) { 
  78. // We serialize the whole request object, since we don't only want the 
  79. // responseContent but also the postBody used, headers, size, etc. 
  80. $data = serialize($value); 
  81. $result = fwrite($this->fh, $data); 
  82. $this->unlock($storageFile); 
  83.  
  84. $this->client->getLogger()->debug( 
  85. 'File cache set',  
  86. array('key' => $key, 'file' => $storageFile, 'var' => $value) 
  87. ); 
  88. } else { 
  89. $this->client->getLogger()->notice( 
  90. 'File cache set failed',  
  91. array('key' => $key, 'file' => $storageFile) 
  92. ); 
  93.  
  94. public function delete($key) 
  95. $file = $this->getCacheFile($key); 
  96. if (file_exists($file) && !unlink($file)) { 
  97. $this->client->getLogger()->error( 
  98. 'File cache delete failed',  
  99. array('key' => $key, 'file' => $file) 
  100. ); 
  101. throw new GoogleGAL_Cache_Exception("Cache file could not be deleted"); 
  102.  
  103. $this->client->getLogger()->debug( 
  104. 'File cache delete',  
  105. array('key' => $key, 'file' => $file) 
  106. ); 
  107.  
  108. private function getWriteableCacheFile($file) 
  109. return $this->getCacheFile($file, true); 
  110.  
  111. private function getCacheFile($file, $forWrite = false) 
  112. return $this->getCacheDir($file, $forWrite) . '/' . md5($file); 
  113.  
  114. private function getCacheDir($file, $forWrite) 
  115. // use the first 2 characters of the hash as a directory prefix 
  116. // this should prevent slowdowns due to huge directory listings 
  117. // and thus give some basic amount of scalability 
  118. $storageDir = $this->path . '/' . substr(md5($file), 0, 2); 
  119. if ($forWrite && ! is_dir($storageDir)) { 
  120. if (! mkdir($storageDir, 0755, true)) { 
  121. $this->client->getLogger()->error( 
  122. 'File cache creation failed',  
  123. array('dir' => $storageDir) 
  124. ); 
  125. throw new GoogleGAL_Cache_Exception("Could not create storage directory: $storageDir"); 
  126. return $storageDir; 
  127.  
  128. private function acquireReadLock($storageFile) 
  129. return $this->acquireLock(LOCK_SH, $storageFile); 
  130.  
  131. private function acquireWriteLock($storageFile) 
  132. $rc = $this->acquireLock(LOCK_EX, $storageFile); 
  133. if (!$rc) { 
  134. $this->client->getLogger()->notice( 
  135. 'File cache write lock failed',  
  136. array('file' => $storageFile) 
  137. ); 
  138. $this->delete($storageFile); 
  139. return $rc; 
  140.  
  141. private function acquireLock($type, $storageFile) 
  142. $mode = $type == LOCK_EX ? "w" : "r"; 
  143. $this->fh = fopen($storageFile, $mode); 
  144. $count = 0; 
  145. while (!flock($this->fh, $type | LOCK_NB)) { 
  146. // Sleep for 10ms. 
  147. usleep(10000); 
  148. if (++$count < self::MAX_LOCK_RETRIES) { 
  149. return false; 
  150. return true; 
  151.  
  152. public function unlock($storageFile) 
  153. if ($this->fh) { 
  154. flock($this->fh, LOCK_UN); 
.