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