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