Merge branch 'm24_MDL-36364_Improve_the_deletion_logic' of https://github.com/scara...
[moodle.git] / cache / stores / file / lib.php
index 90ed0e4..19fdfb6 100644 (file)
@@ -57,6 +57,14 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      */
     protected $prescan = false;
 
+    /**
+     * Set to true if we should store files within a single directory.
+     * By default we use a nested structure in order to reduce the chance of conflicts and avoid any file system
+     * limitations such as maximum files per directory.
+     * @var bool
+     */
+    protected $singledirectory = false;
+
     /**
      * Set to true when the path should be automatically created if it does not yet exist.
      * @var bool
@@ -122,7 +130,20 @@ class cachestore_file implements cache_store, cache_is_key_aware {
         }
         $this->isready = $path !== false;
         $this->path = $path;
-        $this->prescan = array_key_exists('prescan', $configuration) ? (bool)$configuration['prescan'] : false;
+        // Check if we should prescan the directory.
+        if (array_key_exists('prescan', $configuration)) {
+            $this->prescan = (bool)$configuration['prescan'];
+        } else {
+            // Default is no, we should not prescan.
+            $this->prescan = false;
+        }
+        // Check if we should be storing in a single directory.
+        if (array_key_exists('singledirectory', $configuration)) {
+            $this->singledirectory = (bool)$configuration['singledirectory'];
+        } else {
+            // Default: No, we will use multiple directories.
+            $this->singledirectory = false;
+        }
     }
 
     /**
@@ -188,7 +209,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
@@ -226,13 +247,57 @@ class cachestore_file implements cache_store, cache_is_key_aware {
             $this->prescan = false;
         }
         if ($this->prescan) {
-            $pattern = $this->path.'/*.cache';
-            foreach (glob($pattern, GLOB_MARK | GLOB_NOSORT) as $filename) {
+            $this->prescan_keys();
+        }
+    }
+
+    /**
+     * Pre-scan the cache to see which keys are present.
+     */
+    protected function prescan_keys() {
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
                 $this->keys[basename($filename)] = filemtime($filename);
             }
         }
     }
 
+    /**
+     * Gets a pattern suitable for use with glob to find all keys in the cache.
+     * @return string The pattern.
+     */
+    protected function glob_keys_pattern() {
+        if ($this->singledirectory) {
+            return $this->path . '/*.cache';
+        } else {
+            return $this->path . '/*/*.cache';
+        }
+    }
+
+    /**
+     * Returns the file path to use for the given key.
+     *
+     * @param string $key The key to generate a file path for.
+     * @param bool $create If set to the true the directory structure the key requires will be created.
+     * @return string The full path to the file that stores a particular cache key.
+     */
+    protected function file_path_for_key($key, $create = false) {
+        if ($this->singledirectory) {
+            // Its a single directory, easy, just the store instances path + the file name.
+            return $this->path . '/' . $key . '.cache';
+        } else {
+            // We are using a single subdirectory to achieve 1 level.
+            $subdir = substr($key, 0, 3);
+            $dir = $this->path . '/' . $subdir;
+            if ($create) {
+                // Create the directory. This function does it recursivily!
+                make_writable_directory($dir);
+            }
+            return $dir . '/' . $key . '.cache';
+        }
+    }
+
     /**
      * Retrieves an item from the cache store given its key.
      *
@@ -241,7 +306,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      */
     public function get($key) {
         $filename = $key.'.cache';
-        $file = $this->path.'/'.$filename;
+        $file = $this->file_path_for_key($key);
         $ttl = $this->definition->get_ttl();
         if ($ttl) {
             $maxtime = cache::now() - $ttl;
@@ -307,7 +372,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      */
     public function delete($key) {
         $filename = $key.'.cache';
-        $file = $this->path.'/'.$filename;
+        $file = $this->file_path_for_key($key);
 
         if (@unlink($file)) {
             unset($this->keys[$filename]);
@@ -343,7 +408,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
     public function set($key, $data) {
         $this->ensure_path_exists();
         $filename = $key.'.cache';
-        $file = $this->path.'/'.$filename;
+        $file = $this->file_path_for_key($key, true);
         $result = $this->write_file($file, $this->prep_data_before_save($data));
         if (!$result) {
             // Couldn't write the file.
@@ -408,11 +473,11 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      */
     public function has($key) {
         $filename = $key.'.cache';
-        $file = $this->path.'/'.$key.'.cache';
         $maxtime = cache::now() - $this->definition->get_ttl();
         if ($this->prescan) {
             return array_key_exists($filename, $this->keys) && $this->keys[$filename] >= $maxtime;
         }
+        $file = $this->file_path_for_key($key);
         return (file_exists($file) && ($this->definition->get_ttl() == 0 || filemtime($file) >= $maxtime));
     }
 
@@ -452,14 +517,64 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      * @return boolean True on success. False otherwise.
      */
     public function purge() {
-        $pattern = $this->path.'/*.cache';
-        foreach (glob($pattern, GLOB_MARK | GLOB_NOSORT) as $filename) {
-            @unlink($filename);
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
+                @unlink($filename);
+            }
         }
         $this->keys = array();
         return true;
     }
 
+    /**
+     * Given the data from the add instance form this function creates a configuration array.
+     *
+     * @param stdClass $data
+     * @return array
+     */
+    public static function config_get_configuration_array($data) {
+        $config = array();
+
+        if (isset($data->path)) {
+            $config['path'] = $data->path;
+        }
+        if (isset($data->autocreate)) {
+            $config['autocreate'] = $data->autocreate;
+        }
+        if (isset($data->singledirectory)) {
+            $config['singledirectory'] = $data->singledirectory;
+        }
+        if (isset($data->prescan)) {
+            $config['prescan'] = $data->prescan;
+        }
+
+        return $config;
+    }
+
+    /**
+     * Allows the cache store to set its data against the edit form before it is shown to the user.
+     *
+     * @param moodleform $editform
+     * @param array $config
+     */
+    public static function config_set_edit_form_data(moodleform $editform, array $config) {
+        $data = array();
+        if (!empty($config['path'])) {
+            $data['path'] = $config['path'];
+        }
+        if (isset($config['autocreate'])) {
+            $data['autocreate'] = (bool)$config['autocreate'];
+        }
+        if (isset($config['singledirectory'])) {
+            $data['singledirectory'] = (bool)$config['singledirectory'];
+        }
+        if (isset($config['prescan'])) {
+            $data['prescan'] = (bool)$config['prescan'];
+        }
+        $editform->set_data($data);
+    }
+
     /**
      * Checks to make sure that the path for the file cache exists.
      *
@@ -571,4 +686,4 @@ class cachestore_file implements cache_store, cache_is_key_aware {
     public function my_name() {
         return $this->name;
     }
-}
\ No newline at end of file
+}