PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/cache/stores/file/lib.php

https://bitbucket.org/moodle/moodle
PHP | 867 lines | 425 code | 65 blank | 377 comment | 80 complexity | 1878d5f8f7681a1e1229ba69c4c76c93 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * The library file for the file cache store.
  18. *
  19. * This file is part of the file cache store, it contains the API for interacting with an instance of the store.
  20. * This is used as a default cache store within the Cache API. It should never be deleted.
  21. *
  22. * @package cachestore_file
  23. * @category cache
  24. * @copyright 2012 Sam Hemelryk
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26. */
  27. /**
  28. * The file store class.
  29. *
  30. * Configuration options
  31. * path: string: path to the cache directory, if left empty one will be created in the cache directory
  32. * autocreate: true, false
  33. * prescan: true, false
  34. *
  35. * @copyright 2012 Sam Hemelryk
  36. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37. */
  38. class cachestore_file extends cache_store implements cache_is_key_aware, cache_is_configurable, cache_is_searchable {
  39. /**
  40. * The name of the store.
  41. * @var string
  42. */
  43. protected $name;
  44. /**
  45. * The path used to store files for this store and the definition it was initialised with.
  46. * @var string
  47. */
  48. protected $path = false;
  49. /**
  50. * The path in which definition specific sub directories will be created for caching.
  51. * @var string
  52. */
  53. protected $filestorepath = false;
  54. /**
  55. * Set to true when a prescan has been performed.
  56. * @var bool
  57. */
  58. protected $prescan = false;
  59. /**
  60. * Set to true if we should store files within a single directory.
  61. * By default we use a nested structure in order to reduce the chance of conflicts and avoid any file system
  62. * limitations such as maximum files per directory.
  63. * @var bool
  64. */
  65. protected $singledirectory = false;
  66. /**
  67. * Set to true when the path should be automatically created if it does not yet exist.
  68. * @var bool
  69. */
  70. protected $autocreate = false;
  71. /**
  72. * Set to true if a custom path is being used.
  73. * @var bool
  74. */
  75. protected $custompath = false;
  76. /**
  77. * An array of keys we are sure about presently.
  78. * @var array
  79. */
  80. protected $keys = array();
  81. /**
  82. * True when the store is ready to be initialised.
  83. * @var bool
  84. */
  85. protected $isready = false;
  86. /**
  87. * The cache definition this instance has been initialised with.
  88. * @var cache_definition
  89. */
  90. protected $definition;
  91. /**
  92. * Bytes read or written by last call to set()/get() or set_many()/get_many().
  93. *
  94. * @var int
  95. */
  96. protected $lastiobytes = 0;
  97. /**
  98. * A reference to the global $CFG object.
  99. *
  100. * You may be asking yourself why on earth this is here, but there is a good reason.
  101. * By holding onto a reference of the $CFG object we can be absolutely sure that it won't be destroyed before
  102. * we are done with it.
  103. * This makes it possible to use a cache within a destructor method for the purposes of
  104. * delayed writes. Like how the session mechanisms work.
  105. *
  106. * @var stdClass
  107. */
  108. private $cfg = null;
  109. /**
  110. * Constructs the store instance.
  111. *
  112. * Noting that this function is not an initialisation. It is used to prepare the store for use.
  113. * The store will be initialised when required and will be provided with a cache_definition at that time.
  114. *
  115. * @param string $name
  116. * @param array $configuration
  117. */
  118. public function __construct($name, array $configuration = array()) {
  119. global $CFG;
  120. if (isset($CFG)) {
  121. // Hold onto a reference of the global $CFG object.
  122. $this->cfg = $CFG;
  123. }
  124. $this->name = $name;
  125. if (array_key_exists('path', $configuration) && $configuration['path'] !== '') {
  126. $this->custompath = true;
  127. $this->autocreate = !empty($configuration['autocreate']);
  128. $path = (string)$configuration['path'];
  129. if (!is_dir($path)) {
  130. if ($this->autocreate) {
  131. if (!make_writable_directory($path, false)) {
  132. $path = false;
  133. debugging('Error trying to autocreate file store path. '.$path, DEBUG_DEVELOPER);
  134. }
  135. } else {
  136. $path = false;
  137. debugging('The given file cache store path does not exist. '.$path, DEBUG_DEVELOPER);
  138. }
  139. }
  140. if ($path !== false && !is_writable($path)) {
  141. $path = false;
  142. debugging('The file cache store path is not writable for `'.$name.'`', DEBUG_DEVELOPER);
  143. }
  144. } else {
  145. $path = make_cache_directory('cachestore_file/'.preg_replace('#[^a-zA-Z0-9\.\-_]+#', '', $name));
  146. }
  147. $this->isready = $path !== false;
  148. $this->filestorepath = $path;
  149. // This will be updated once the store has been initialised for a definition.
  150. $this->path = $path;
  151. // Check if we should prescan the directory.
  152. if (array_key_exists('prescan', $configuration)) {
  153. $this->prescan = (bool)$configuration['prescan'];
  154. } else {
  155. // Default is no, we should not prescan.
  156. $this->prescan = false;
  157. }
  158. // Check if we should be storing in a single directory.
  159. if (array_key_exists('singledirectory', $configuration)) {
  160. $this->singledirectory = (bool)$configuration['singledirectory'];
  161. } else {
  162. // Default: No, we will use multiple directories.
  163. $this->singledirectory = false;
  164. }
  165. }
  166. /**
  167. * Performs any necessary operation when the file store instance has been created.
  168. */
  169. public function instance_created() {
  170. if ($this->isready && !$this->prescan) {
  171. // It is supposed the store instance to expect an empty folder.
  172. $this->purge_all_definitions();
  173. }
  174. }
  175. /**
  176. * Returns true if this store instance is ready to be used.
  177. * @return bool
  178. */
  179. public function is_ready() {
  180. return $this->isready;
  181. }
  182. /**
  183. * Returns true once this instance has been initialised.
  184. *
  185. * @return bool
  186. */
  187. public function is_initialised() {
  188. return true;
  189. }
  190. /**
  191. * Returns the supported features as a combined int.
  192. *
  193. * @param array $configuration
  194. * @return int
  195. */
  196. public static function get_supported_features(array $configuration = array()) {
  197. $supported = self::SUPPORTS_DATA_GUARANTEE +
  198. self::SUPPORTS_NATIVE_TTL +
  199. self::IS_SEARCHABLE +
  200. self::DEREFERENCES_OBJECTS;
  201. return $supported;
  202. }
  203. /**
  204. * Returns false as this store does not support multiple identifiers.
  205. * (This optional function is a performance optimisation; it must be
  206. * consistent with the value from get_supported_features.)
  207. *
  208. * @return bool False
  209. */
  210. public function supports_multiple_identifiers() {
  211. return false;
  212. }
  213. /**
  214. * Returns the supported modes as a combined int.
  215. *
  216. * @param array $configuration
  217. * @return int
  218. */
  219. public static function get_supported_modes(array $configuration = array()) {
  220. return self::MODE_APPLICATION + self::MODE_SESSION;
  221. }
  222. /**
  223. * Returns true if the store requirements are met.
  224. *
  225. * @return bool
  226. */
  227. public static function are_requirements_met() {
  228. return true;
  229. }
  230. /**
  231. * Returns true if the given mode is supported by this store.
  232. *
  233. * @param int $mode One of cache_store::MODE_*
  234. * @return bool
  235. */
  236. public static function is_supported_mode($mode) {
  237. return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
  238. }
  239. /**
  240. * Initialises the cache.
  241. *
  242. * Once this has been done the cache is all set to be used.
  243. *
  244. * @param cache_definition $definition
  245. */
  246. public function initialise(cache_definition $definition) {
  247. $this->definition = $definition;
  248. $hash = preg_replace('#[^a-zA-Z0-9]+#', '_', $this->definition->get_id());
  249. $this->path = $this->filestorepath.'/'.$hash;
  250. make_writable_directory($this->path, false);
  251. if ($this->prescan && $definition->get_mode() !== self::MODE_REQUEST) {
  252. $this->prescan = false;
  253. }
  254. if ($this->prescan) {
  255. $this->prescan_keys();
  256. }
  257. }
  258. /**
  259. * Pre-scan the cache to see which keys are present.
  260. */
  261. protected function prescan_keys() {
  262. $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
  263. if (is_array($files)) {
  264. foreach ($files as $filename) {
  265. $this->keys[basename($filename)] = filemtime($filename);
  266. }
  267. }
  268. }
  269. /**
  270. * Gets a pattern suitable for use with glob to find all keys in the cache.
  271. *
  272. * @param string $prefix A prefix to use.
  273. * @return string The pattern.
  274. */
  275. protected function glob_keys_pattern($prefix = '') {
  276. if ($this->singledirectory) {
  277. return $this->path . '/'.$prefix.'*.cache';
  278. } else {
  279. return $this->path . '/*/'.$prefix.'*.cache';
  280. }
  281. }
  282. /**
  283. * Returns the file path to use for the given key.
  284. *
  285. * @param string $key The key to generate a file path for.
  286. * @param bool $create If set to the true the directory structure the key requires will be created.
  287. * @return string The full path to the file that stores a particular cache key.
  288. */
  289. protected function file_path_for_key($key, $create = false) {
  290. if ($this->singledirectory) {
  291. // Its a single directory, easy, just the store instances path + the file name.
  292. return $this->path . '/' . $key . '.cache';
  293. } else {
  294. // We are using a single subdirectory to achieve 1 level.
  295. // We suffix the subdir so it does not clash with any windows
  296. // reserved filenames like 'con'.
  297. $subdir = substr($key, 0, 3) . '-cache';
  298. $dir = $this->path . '/' . $subdir;
  299. if ($create) {
  300. // Create the directory. This function does it recursivily!
  301. make_writable_directory($dir, false);
  302. }
  303. return $dir . '/' . $key . '.cache';
  304. }
  305. }
  306. /**
  307. * Retrieves an item from the cache store given its key.
  308. *
  309. * @param string $key The key to retrieve
  310. * @return mixed The data that was associated with the key, or false if the key did not exist.
  311. */
  312. public function get($key) {
  313. $this->lastiobytes = 0;
  314. $filename = $key.'.cache';
  315. $file = $this->file_path_for_key($key);
  316. $ttl = $this->definition->get_ttl();
  317. $maxtime = 0;
  318. if ($ttl) {
  319. $maxtime = cache::now() - $ttl;
  320. }
  321. $readfile = false;
  322. if ($this->prescan && array_key_exists($filename, $this->keys)) {
  323. if ((!$ttl || $this->keys[$filename] >= $maxtime) && file_exists($file)) {
  324. $readfile = true;
  325. } else {
  326. $this->delete($key);
  327. }
  328. } else if (file_exists($file) && (!$ttl || filemtime($file) >= $maxtime)) {
  329. $readfile = true;
  330. }
  331. if (!$readfile) {
  332. return false;
  333. }
  334. // Open ensuring the file for reading in binary format.
  335. if (!$handle = fopen($file, 'rb')) {
  336. return false;
  337. }
  338. // Lock it up!
  339. // We don't care if this succeeds or not, on some systems it will, on some it won't, meah either way.
  340. flock($handle, LOCK_SH);
  341. $data = '';
  342. // Read the data in 1Mb chunks. Small caches will not loop more than once. We don't use filesize as it may
  343. // be cached with a different value than what we need to read from the file.
  344. do {
  345. $data .= fread($handle, 1048576);
  346. } while (!feof($handle));
  347. // Unlock it.
  348. flock($handle, LOCK_UN);
  349. $this->lastiobytes = strlen($data);
  350. // Return it unserialised.
  351. return $this->prep_data_after_read($data);
  352. }
  353. /**
  354. * Retrieves several items from the cache store in a single transaction.
  355. *
  356. * If not all of the items are available in the cache then the data value for those that are missing will be set to false.
  357. *
  358. * @param array $keys The array of keys to retrieve
  359. * @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
  360. * be set to false.
  361. */
  362. public function get_many($keys) {
  363. $result = array();
  364. $total = 0;
  365. foreach ($keys as $key) {
  366. $result[$key] = $this->get($key);
  367. $total += $this->lastiobytes;
  368. }
  369. $this->lastiobytes = $total;
  370. return $result;
  371. }
  372. /**
  373. * Gets bytes read by last get() or get_many(), or written by set() or set_many().
  374. *
  375. * @return int Bytes read or written
  376. * @since Moodle 4.0
  377. */
  378. public function get_last_io_bytes(): int {
  379. return $this->lastiobytes;
  380. }
  381. /**
  382. * Deletes an item from the cache store.
  383. *
  384. * @param string $key The key to delete.
  385. * @return bool Returns true if the operation was a success, false otherwise.
  386. */
  387. public function delete($key) {
  388. $filename = $key.'.cache';
  389. $file = $this->file_path_for_key($key);
  390. if (file_exists($file) && @unlink($file)) {
  391. unset($this->keys[$filename]);
  392. return true;
  393. }
  394. return false;
  395. }
  396. /**
  397. * Deletes several keys from the cache in a single action.
  398. *
  399. * @param array $keys The keys to delete
  400. * @return int The number of items successfully deleted.
  401. */
  402. public function delete_many(array $keys) {
  403. $count = 0;
  404. foreach ($keys as $key) {
  405. if ($this->delete($key)) {
  406. $count++;
  407. }
  408. }
  409. return $count;
  410. }
  411. /**
  412. * Sets an item in the cache given its key and data value.
  413. *
  414. * @param string $key The key to use.
  415. * @param mixed $data The data to set.
  416. * @return bool True if the operation was a success false otherwise.
  417. */
  418. public function set($key, $data) {
  419. $this->ensure_path_exists();
  420. $filename = $key.'.cache';
  421. $file = $this->file_path_for_key($key, true);
  422. $serialized = $this->prep_data_before_save($data);
  423. $this->lastiobytes = strlen($serialized);
  424. $result = $this->write_file($file, $serialized);
  425. if (!$result) {
  426. // Couldn't write the file.
  427. return false;
  428. }
  429. // Record the key if required.
  430. if ($this->prescan) {
  431. $this->keys[$filename] = cache::now() + 1;
  432. }
  433. // Return true.. it all worked **miracles**.
  434. return true;
  435. }
  436. /**
  437. * Prepares data to be stored in a file.
  438. *
  439. * @param mixed $data
  440. * @return string
  441. */
  442. protected function prep_data_before_save($data) {
  443. return serialize($data);
  444. }
  445. /**
  446. * Prepares the data it has been read from the cache. Undoing what was done in prep_data_before_save.
  447. *
  448. * @param string $data
  449. * @return mixed
  450. * @throws coding_exception
  451. */
  452. protected function prep_data_after_read($data) {
  453. $result = @unserialize($data);
  454. if ($result === false) {
  455. throw new coding_exception('Failed to unserialise data from file. Either failed to read, or failed to write.');
  456. }
  457. return $result;
  458. }
  459. /**
  460. * Sets many items in the cache in a single transaction.
  461. *
  462. * @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
  463. * keys, 'key' and 'value'.
  464. * @return int The number of items successfully set. It is up to the developer to check this matches the number of items
  465. * sent ... if they care that is.
  466. */
  467. public function set_many(array $keyvaluearray) {
  468. $count = 0;
  469. $totaliobytes = 0;
  470. foreach ($keyvaluearray as $pair) {
  471. if ($this->set($pair['key'], $pair['value'])) {
  472. $totaliobytes += $this->lastiobytes;
  473. $count++;
  474. }
  475. }
  476. $this->lastiobytes = $totaliobytes;
  477. return $count;
  478. }
  479. /**
  480. * Checks if the store has a record for the given key and returns true if so.
  481. *
  482. * @param string $key
  483. * @return bool
  484. */
  485. public function has($key) {
  486. $filename = $key.'.cache';
  487. $maxtime = cache::now() - $this->definition->get_ttl();
  488. if ($this->prescan) {
  489. return array_key_exists($filename, $this->keys) && $this->keys[$filename] >= $maxtime;
  490. }
  491. $file = $this->file_path_for_key($key);
  492. return (file_exists($file) && ($this->definition->get_ttl() == 0 || filemtime($file) >= $maxtime));
  493. }
  494. /**
  495. * Returns true if the store contains records for all of the given keys.
  496. *
  497. * @param array $keys
  498. * @return bool
  499. */
  500. public function has_all(array $keys) {
  501. foreach ($keys as $key) {
  502. if (!$this->has($key)) {
  503. return false;
  504. }
  505. }
  506. return true;
  507. }
  508. /**
  509. * Returns true if the store contains records for any of the given keys.
  510. *
  511. * @param array $keys
  512. * @return bool
  513. */
  514. public function has_any(array $keys) {
  515. foreach ($keys as $key) {
  516. if ($this->has($key)) {
  517. return true;
  518. }
  519. }
  520. return false;
  521. }
  522. /**
  523. * Purges the cache definition deleting all the items within it.
  524. *
  525. * @return boolean True on success. False otherwise.
  526. */
  527. public function purge() {
  528. if ($this->isready) {
  529. $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
  530. if (is_array($files)) {
  531. foreach ($files as $filename) {
  532. @unlink($filename);
  533. }
  534. }
  535. $this->keys = array();
  536. }
  537. return true;
  538. }
  539. /**
  540. * Purges all the cache definitions deleting all items within them.
  541. *
  542. * @return boolean True on success. False otherwise.
  543. */
  544. protected function purge_all_definitions() {
  545. // Warning: limit the deletion to what file store is actually able
  546. // to create using the internal {@link purge()} providing the
  547. // {@link $path} with a wildcard to perform a purge action over all the definitions.
  548. $currpath = $this->path;
  549. $this->path = $this->filestorepath.'/*';
  550. $result = $this->purge();
  551. $this->path = $currpath;
  552. return $result;
  553. }
  554. /**
  555. * Given the data from the add instance form this function creates a configuration array.
  556. *
  557. * @param stdClass $data
  558. * @return array
  559. */
  560. public static function config_get_configuration_array($data) {
  561. $config = array();
  562. if (isset($data->path)) {
  563. $config['path'] = $data->path;
  564. }
  565. if (isset($data->autocreate)) {
  566. $config['autocreate'] = $data->autocreate;
  567. }
  568. if (isset($data->singledirectory)) {
  569. $config['singledirectory'] = $data->singledirectory;
  570. }
  571. if (isset($data->prescan)) {
  572. $config['prescan'] = $data->prescan;
  573. }
  574. return $config;
  575. }
  576. /**
  577. * Allows the cache store to set its data against the edit form before it is shown to the user.
  578. *
  579. * @param moodleform $editform
  580. * @param array $config
  581. */
  582. public static function config_set_edit_form_data(moodleform $editform, array $config) {
  583. $data = array();
  584. if (!empty($config['path'])) {
  585. $data['path'] = $config['path'];
  586. }
  587. if (isset($config['autocreate'])) {
  588. $data['autocreate'] = (bool)$config['autocreate'];
  589. }
  590. if (isset($config['singledirectory'])) {
  591. $data['singledirectory'] = (bool)$config['singledirectory'];
  592. }
  593. if (isset($config['prescan'])) {
  594. $data['prescan'] = (bool)$config['prescan'];
  595. }
  596. $editform->set_data($data);
  597. }
  598. /**
  599. * Checks to make sure that the path for the file cache exists.
  600. *
  601. * @return bool
  602. * @throws coding_exception
  603. */
  604. protected function ensure_path_exists() {
  605. global $CFG;
  606. if (!is_writable($this->path)) {
  607. if ($this->custompath && !$this->autocreate) {
  608. throw new coding_exception('File store path does not exist. It must exist and be writable by the web server.');
  609. }
  610. $createdcfg = false;
  611. if (!isset($CFG)) {
  612. // This can only happen during destruction of objects.
  613. // A cache is being used within a destructor, php is ending a request and $CFG has
  614. // already being cleaned up.
  615. // Rebuild $CFG with directory permissions just to complete this write.
  616. $CFG = $this->cfg;
  617. $createdcfg = true;
  618. }
  619. if (!make_writable_directory($this->path, false)) {
  620. throw new coding_exception('File store path does not exist and can not be created.');
  621. }
  622. if ($createdcfg) {
  623. // We re-created it so we'll clean it up.
  624. unset($CFG);
  625. }
  626. }
  627. return true;
  628. }
  629. /**
  630. * Performs any necessary clean up when the file store instance is being deleted.
  631. *
  632. * 1. Purges the cache directory.
  633. * 2. Deletes the directory we created for the given definition.
  634. */
  635. public function instance_deleted() {
  636. $this->purge_all_definitions();
  637. @rmdir($this->filestorepath);
  638. }
  639. /**
  640. * Generates an instance of the cache store that can be used for testing.
  641. *
  642. * Returns an instance of the cache store, or false if one cannot be created.
  643. *
  644. * @param cache_definition $definition
  645. * @return cachestore_file
  646. */
  647. public static function initialise_test_instance(cache_definition $definition) {
  648. $name = 'File test';
  649. $path = make_cache_directory('cachestore_file_test');
  650. $cache = new cachestore_file($name, array('path' => $path));
  651. if ($cache->is_ready()) {
  652. $cache->initialise($definition);
  653. }
  654. return $cache;
  655. }
  656. /**
  657. * Generates the appropriate configuration required for unit testing.
  658. *
  659. * @return array Array of unit test configuration data to be used by initialise().
  660. */
  661. public static function unit_test_configuration() {
  662. return array();
  663. }
  664. /**
  665. * Writes your madness to a file.
  666. *
  667. * There are several things going on in this function to try to ensure what we don't end up with partial writes etc.
  668. * 1. Files for writing are opened with the mode xb, the file must be created and can not already exist.
  669. * 2. Renaming, data is written to a temporary file, where it can be verified using md5 and is then renamed.
  670. *
  671. * @param string $file Absolute file path
  672. * @param string $content The content to write.
  673. * @return bool
  674. */
  675. protected function write_file($file, $content) {
  676. // Generate a temp file that is going to be unique. We'll rename it at the end to the desired file name.
  677. // in this way we avoid partial writes.
  678. $path = dirname($file);
  679. while (true) {
  680. $tempfile = $path.'/'.uniqid(sesskey().'.', true) . '.temp';
  681. if (!file_exists($tempfile)) {
  682. break;
  683. }
  684. }
  685. // Open the file with mode=x. This acts to create and open the file for writing only.
  686. // If the file already exists this will return false.
  687. // We also force binary.
  688. $handle = @fopen($tempfile, 'xb+');
  689. if ($handle === false) {
  690. // File already exists... lock already exists, return false.
  691. return false;
  692. }
  693. fwrite($handle, $content);
  694. fflush($handle);
  695. // Close the handle, we're done.
  696. fclose($handle);
  697. if (md5_file($tempfile) !== md5($content)) {
  698. // The md5 of the content of the file must match the md5 of the content given to be written.
  699. @unlink($tempfile);
  700. return false;
  701. }
  702. // Finally rename the temp file to the desired file, returning the true|false result.
  703. $result = rename($tempfile, $file);
  704. @chmod($file, $this->cfg->filepermissions);
  705. if (!$result) {
  706. // Failed to rename, don't leave files lying around.
  707. @unlink($tempfile);
  708. }
  709. return $result;
  710. }
  711. /**
  712. * Returns the name of this instance.
  713. * @return string
  714. */
  715. public function my_name() {
  716. return $this->name;
  717. }
  718. /**
  719. * Finds all of the keys being used by this cache store instance.
  720. *
  721. * @return array
  722. */
  723. public function find_all() {
  724. $this->ensure_path_exists();
  725. $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
  726. $return = array();
  727. if ($files === false) {
  728. return $return;
  729. }
  730. foreach ($files as $file) {
  731. $return[] = substr(basename($file), 0, -6);
  732. }
  733. return $return;
  734. }
  735. /**
  736. * Finds all of the keys whose keys start with the given prefix.
  737. *
  738. * @param string $prefix
  739. */
  740. public function find_by_prefix($prefix) {
  741. $this->ensure_path_exists();
  742. $prefix = preg_replace('#(\*|\?|\[)#', '[$1]', $prefix);
  743. $files = glob($this->glob_keys_pattern($prefix), GLOB_MARK | GLOB_NOSORT);
  744. $return = array();
  745. if ($files === false) {
  746. return $return;
  747. }
  748. foreach ($files as $file) {
  749. // Trim off ".cache" from the end.
  750. $return[] = substr(basename($file), 0, -6);
  751. }
  752. return $return;
  753. }
  754. /**
  755. * Gets total size for the directory used by the cache store.
  756. *
  757. * @return int Total size in bytes
  758. */
  759. public function store_total_size(): ?int {
  760. return get_directory_size($this->filestorepath);
  761. }
  762. /**
  763. * Gets total size for a specific cache.
  764. *
  765. * With the file cache we can just look at the directory listing without having to
  766. * actually load any files, so the $samplekeys parameter is ignored.
  767. *
  768. * @param int $samplekeys Unused
  769. * @return stdClass Cache details
  770. */
  771. public function cache_size_details(int $samplekeys = 50): stdClass {
  772. $result = (object)[
  773. 'supported' => true,
  774. 'items' => 0,
  775. 'mean' => 0,
  776. 'sd' => 0,
  777. 'margin' => 0
  778. ];
  779. // Find all the files in this cache.
  780. $this->ensure_path_exists();
  781. $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
  782. if ($files === false || count($files) === 0) {
  783. return $result;
  784. }
  785. // Get the sizes and count of files.
  786. $sizes = [];
  787. foreach ($files as $file) {
  788. $result->items++;
  789. $sizes[] = filesize($file);
  790. }
  791. // Work out mean and standard deviation.
  792. $total = array_sum($sizes);
  793. $result->mean = $total / $result->items;
  794. $squarediff = 0;
  795. foreach ($sizes as $size) {
  796. $squarediff += ($size - $result->mean) ** 2;
  797. }
  798. $squarediff /= $result->items;
  799. $result->sd = sqrt($squarediff);
  800. return $result;
  801. }
  802. }