<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2007 - 2016 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flCacheFile Class
 *
 * Implementation of file based cache engine, all cache files are stored in the folder
 * {FLSYSPATH}/cache/data/{0-9a-f}/{0-9a-f}/{id.php}
 *
 * @package fusionLib
 */
class flCacheFile extends flCache {

	/**
	 * The header for cached files.
	 */
	const HEADER = '<?php __halt_compiler(); ?>';

	/**
	 * Store an element in the cache.
	 *
	 * @param string $id The id to store the element as.
	 * @param mixed $data The data to store.
	 * @param int $lifetime The lifetime of the object in seconds, null to use the default or 0 to never expire.
	 * @return bool true if the data is cached or false if it could not be cached.
	 */
	function store($id, $data, $lifetime = null) {

		// Calculate expire time
		$expire = $lifetime === null ? $this->lifetime : $lifetime;
		if($expire)
			$expire += time();

		// Hash the id and create the nested path
		$idHash = md5($id);
		$path = FLSYSPATH . 'cache/data/' . $idHash[0] . '/' . $idHash[1] . '/';

		// Create the folder if not already present
		if(!is_writable($path)) {
			if(!@mkdir($path, 0770, true))
				return false;
		}

		// Delete any existing cache entry for the id
		$path .= $idHash . '-';
 		$files = glob($path . '*.php', GLOB_NOSORT);
		foreach($files as $file)
			@unlink($file);

		// Write the cache file
		$result = file_put_contents($path . $expire . '.php', flCacheFile::HEADER . serialize($data), LOCK_EX) !== false;

		// Randomly choose to clean cache
 		static $autoClean = true;
		if($autoClean && mt_rand(1, 100) == 1) {
			$autoClean = false;
			$top = glob(FLSYSPATH . 'cache/data/*', GLOB_NOSORT | GLOB_ONLYDIR);
			foreach($top as $td) {
				$second = glob("$td/*", GLOB_NOSORT | GLOB_ONLYDIR);
				foreach($second as $sd) {
					$offset = strlen($sd) + 34;
					$files = glob("$sd/*.php", GLOB_NOSORT);
					foreach($files as $f) {
						$expire = substr($f, $offset, -4);
						if($expire != 0 && $expire < time())
							@unlink($f);
					}
					@rmdir($sd);
				}
				@rmdir($td);
			}
		}

		// Return result
		return $result;
	}

	/**
	 * Get the element from the cache.
	 *
	 * @param string $id The id of the element to get.
	 * @return mixed false if the element is not cached or has expired; else the cached data.
	 */
	function get($id) {
		$idHash = md5($id);
		$path = FLSYSPATH . 'cache/data/' . $idHash[0] . '/' . $idHash[1] . '/' . $idHash . '-';
		$offset = strlen($path);

		// Get a list of all files that match
		$files = glob($path . '*.php', GLOB_NOSORT);

		// If no files in the list then cache miss
		if(!count($files))
			return false;

		// Get the 1st in the list and extract it's parts
		$parts =  substr($files[0], $offset, -4);

		// If expired delete it
		if($parts !== 0 && $parts < time()) {
			@unlink($files[0]);
			return false;
		}

		// Get the file contents
		if(($d = file_get_contents($files[0], null, null, strlen(flCacheFile::HEADER))) === false)
			return false;
		else
			return unserialize($d);
	}

	/**
	 * Remove the given element from the cache.
	 *
	 * @param string $id The id of the element to remove.
	 */
	function remove($id) {
		$idHash = md5($id);
		$files = glob(FLSYSPATH . 'cache/data/' . $idHash[0] . '/' . $idHash[1] . "/$idHash-*.php", GLOB_NOSORT);
		foreach($files as $file)
			@unlink($file);
	}

	/**
	 * Flush the cache.
	 */
	function flush() {
		$top = glob(FLSYSPATH . 'cache/data/*', GLOB_NOSORT | GLOB_ONLYDIR);
		foreach($top as $td) {
			$second = glob("$td/*", GLOB_NOSORT | GLOB_ONLYDIR);
			foreach($second as $sd) {
				$files = glob("$sd/*.php", GLOB_NOSORT);
				foreach($files as $f)
					@unlink($f);
				@rmdir($sd);
			}
			@rmdir($td);
		}
	}
}
