PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Google/Cache/File.php

https://gitlab.com/crsr/google-api-php-client
PHP | 259 lines | 198 code | 26 blank | 35 comment | 26 complexity | a2c6ab52a6a4dcf324c3db159dc3b436 MD5 | raw file
  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. use Google\Auth\CacheInterface;
  18. use Psr\Log\LoggerInterface;
  19. /*
  20. * This class implements a basic on disk storage. While that does
  21. * work quite well it's not the most elegant and scalable solution.
  22. * It will also get you into a heap of trouble when you try to run
  23. * this in a clustered environment.
  24. *
  25. * @author Chris Chabot <chabotc@google.com>
  26. */
  27. class Google_Cache_File implements CacheInterface
  28. {
  29. const MAX_LOCK_RETRIES = 10;
  30. private $path;
  31. private $fh;
  32. /**
  33. * @var use Psr\Log\LoggerInterface logger
  34. */
  35. private $logger;
  36. public function __construct($path, LoggerInterface $logger = null)
  37. {
  38. $this->path = $path;
  39. $this->logger = $logger;
  40. }
  41. public function get($key, $expiration = false)
  42. {
  43. $storageFile = $this->getCacheFile($key);
  44. $data = false;
  45. if (!file_exists($storageFile)) {
  46. $this->log(
  47. '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->log(
  57. 'debug',
  58. 'File cache miss (expired)',
  59. array('key' => $key, 'file' => $storageFile)
  60. );
  61. $this->delete($key);
  62. return false;
  63. }
  64. }
  65. if ($this->acquireReadLock($storageFile)) {
  66. if (filesize($storageFile) > 0) {
  67. $data = fread($this->fh, filesize($storageFile));
  68. $data = unserialize($data);
  69. } else {
  70. $this->log(
  71. 'debug',
  72. 'Cache file was empty',
  73. array('file' => $storageFile)
  74. );
  75. }
  76. $this->unlock();
  77. }
  78. $this->log(
  79. 'debug',
  80. 'File cache hit',
  81. array('key' => $key, 'file' => $storageFile, 'var' => $data)
  82. );
  83. return $data;
  84. }
  85. public function set($key, $value)
  86. {
  87. $storageFile = $this->getWriteableCacheFile($key);
  88. if ($this->acquireWriteLock($storageFile)) {
  89. // We serialize the whole request object, since we don't only want the
  90. // responseContent but also the postBody used, headers, size, etc.
  91. $data = serialize($value);
  92. fwrite($this->fh, $data);
  93. $this->unlock();
  94. $this->log(
  95. 'debug',
  96. 'File cache set',
  97. array('key' => $key, 'file' => $storageFile, 'var' => $value)
  98. );
  99. } else {
  100. $this->log(
  101. 'notice',
  102. 'File cache set failed',
  103. array('key' => $key, 'file' => $storageFile)
  104. );
  105. }
  106. }
  107. public function delete($key)
  108. {
  109. $file = $this->getCacheFile($key);
  110. if (file_exists($file) && !unlink($file)) {
  111. $this->log(
  112. 'error',
  113. 'File cache delete failed',
  114. array('key' => $key, 'file' => $file)
  115. );
  116. throw new Google_Cache_Exception("Cache file could not be deleted");
  117. }
  118. $this->log(
  119. 'debug',
  120. 'File cache delete',
  121. array('key' => $key, 'file' => $file)
  122. );
  123. }
  124. private function getWriteableCacheFile($file)
  125. {
  126. return $this->getCacheFile($file, true);
  127. }
  128. private function getCacheFile($file, $forWrite = false)
  129. {
  130. return $this->getCacheDir($file, $forWrite) . '/' . md5($file);
  131. }
  132. private function getCacheDir($file, $forWrite)
  133. {
  134. // use the first 2 characters of the hash as a directory prefix
  135. // this should prevent slowdowns due to huge directory listings
  136. // and thus give some basic amount of scalability
  137. $fileHash = substr(md5($file), 0, 2);
  138. $processUser = null;
  139. if (function_exists('posix_geteuid')) {
  140. $processUser = posix_getpwuid(posix_geteuid())['name'];
  141. } elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  142. $processUser = get_current_user();
  143. }
  144. if (empty($processUser)) {
  145. $this->log(
  146. 'notice',
  147. 'Process User get failed'
  148. );
  149. }
  150. $userHash = md5($processUser);
  151. $dirHash = $userHash . DIRECTORY_SEPARATOR . $fileHash;
  152. // trim the directory separator from the path to prevent double separators
  153. $rootCacheDir = rtrim($this->path, DIRECTORY_SEPARATOR);
  154. $storageDir = $rootCacheDir . DIRECTORY_SEPARATOR . $dirHash;
  155. if ($forWrite && !is_dir($storageDir)) {
  156. // create root dir
  157. if (!is_dir($rootCacheDir)) {
  158. if (!mkdir($rootCacheDir, 0777, true)) {
  159. $this->log(
  160. 'error',
  161. 'File cache creation failed',
  162. array('dir' => $rootCacheDir)
  163. );
  164. throw new Google_Cache_Exception("Could not create cache directory: $rootCacheDir");
  165. }
  166. }
  167. // create dir for file
  168. if (!mkdir($storageDir, 0700, true)) {
  169. $this->log(
  170. 'error',
  171. 'File cache creation failed',
  172. array('dir' => $storageDir)
  173. );
  174. throw new Google_Cache_Exception("Could not create cache directory: $storageDir");
  175. }
  176. }
  177. return $storageDir;
  178. }
  179. private function acquireReadLock($storageFile)
  180. {
  181. return $this->acquireLock(LOCK_SH, $storageFile);
  182. }
  183. private function acquireWriteLock($storageFile)
  184. {
  185. $rc = $this->acquireLock(LOCK_EX, $storageFile);
  186. if (!$rc) {
  187. $this->log(
  188. 'notice',
  189. 'File cache write lock failed',
  190. array('file' => $storageFile)
  191. );
  192. $this->delete($storageFile);
  193. }
  194. return $rc;
  195. }
  196. private function acquireLock($type, $storageFile)
  197. {
  198. $mode = $type == LOCK_EX ? "w" : "r";
  199. $this->fh = fopen($storageFile, $mode);
  200. if (!$this->fh) {
  201. $this->log(
  202. 'error',
  203. 'Failed to open file during lock acquisition',
  204. array('file' => $storageFile)
  205. );
  206. return false;
  207. }
  208. if ($type == LOCK_EX) {
  209. chmod($storageFile, 0600);
  210. }
  211. $count = 0;
  212. while (!flock($this->fh, $type | LOCK_NB)) {
  213. // Sleep for 10ms.
  214. usleep(10000);
  215. if (++$count < self::MAX_LOCK_RETRIES) {
  216. return false;
  217. }
  218. }
  219. return true;
  220. }
  221. public function unlock()
  222. {
  223. if ($this->fh) {
  224. flock($this->fh, LOCK_UN);
  225. }
  226. }
  227. private function log($level, $message, $context = array())
  228. {
  229. if ($this->logger) {
  230. $this->logger->log($level, $message, $context);
  231. }
  232. }
  233. }