/wcfsetup/install/files/lib/util/DirectoryUtil.class.php

https://github.com/KomHunter2/WCF · PHP · 339 lines · 158 code · 50 blank · 131 comment · 47 complexity · 217f172e62ddc03be1794b9f0090fd4e MD5 · raw file

  1. <?php
  2. namespace wcf\util;
  3. use wcf\system\exception\SystemException;
  4. /**
  5. * Contains directory-related functions
  6. *
  7. * @author Tim Düsterhus
  8. * @copyright 2011 Tim Düsterhus
  9. * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  10. * @package com.woltlab.wcf
  11. * @subpackage util
  12. * @category Community Framework
  13. */
  14. class DirectoryUtil {
  15. /**
  16. * @var \DirectoryIterator
  17. */
  18. protected $obj = null;
  19. /**
  20. * all files with full path
  21. *
  22. * @var array<string>
  23. */
  24. protected $files = array();
  25. /**
  26. * all files with filename as key and DirectoryIterator object as value
  27. *
  28. * @var array<\DirectoryIterator>
  29. */
  30. protected $fileObjects = array();
  31. /**
  32. * directory size in bytes
  33. *
  34. * @var integer
  35. */
  36. protected $size = 0;
  37. /**
  38. * directory path
  39. *
  40. * @var string
  41. */
  42. protected $directory = '';
  43. /**
  44. * determines whether scan should be recursive
  45. *
  46. * @var boolean
  47. */
  48. protected $recursive = true;
  49. /**
  50. * indicates that files won't be sorted
  51. *
  52. * @var integer
  53. */
  54. const SORT_NONE = -1;
  55. /**
  56. * all recursive and non-recursive instances of DirectoryUtil
  57. *
  58. * @var array<array>
  59. */
  60. protected static $instances = array(
  61. true => array(), // recursive instances
  62. false => array() // non-recursive instances
  63. );
  64. /**
  65. * Returns an instance of DirectoryUtil (or child).
  66. *
  67. * @param string $directory path
  68. * @param boolean $recursive walk through sub-directories too
  69. * @return wcf\util\DirectoryUtil
  70. */
  71. public static function getInstance($tmpDirectory, $recursive = true) {
  72. $directory = realpath(FileUtil::unifyDirSeperator($tmpDirectory));
  73. // realpath returns false if the directory does not exist
  74. if ($directory === false) {
  75. throw new SystemException("Unknown directory '".$tmpDirectory."'");
  76. }
  77. if (!is_dir($directory)) {
  78. throw new SystemException("'".$tmpDirectory."' is no directory");
  79. }
  80. if (!isset(static::$instances[$recursive][$directory])) {
  81. static::$instances[$recursive][$directory] = new static($directory, $recursive);
  82. }
  83. return static::$instances[$recursive][$directory];
  84. }
  85. /**
  86. * Creates a new instance of DirectoryUtil.
  87. *
  88. * @param string $directory directory path
  89. * @param boolean $recursive created a recursive directory iterator
  90. * @see wcf\util\DirectoryUtil::getInstance()
  91. */
  92. protected function __construct($directory, $recursive = true) {
  93. $this->directory = $directory;
  94. $this->recursive = $recursive;
  95. // handle iterator type
  96. if ($this->recursive) {
  97. $this->obj = new \RecursiveDirectoryIterator($directory);
  98. }
  99. else {
  100. $this->obj = new \DirectoryIterator($directory);
  101. }
  102. }
  103. /**
  104. * @see wcf\util\DirectoryUtil::getInstance()
  105. */
  106. private final function __clone() {}
  107. /**
  108. * Returns a sorted list of files.
  109. *
  110. * @param integer $order sort-order
  111. * @param string $pattern pattern to match
  112. * @param boolean $negativeMatch true if the pattern should be inversed
  113. * @return array<string>
  114. */
  115. public function getFiles($order = SORT_ASC, $pattern = '', $negativeMatch = false) {
  116. // scan the folder
  117. $this->scanFiles();
  118. $files = $this->files;
  119. // sort out non matching files
  120. if (!empty($pattern)) {
  121. foreach ($files as $filename => $value) {
  122. if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($files[$filename]);
  123. }
  124. }
  125. if ($order == SORT_DESC) {
  126. krsort($files, $order);
  127. }
  128. else if ($order == SORT_ASC) {
  129. ksort($files, $order);
  130. }
  131. else if ($order == self::SORT_NONE) {
  132. // nothing to do here :)
  133. }
  134. else {
  135. throw new SystemException('The given sorting is not supported');
  136. }
  137. return $files;
  138. }
  139. /**
  140. * Returns a sorted list of files, with DirectoryIterator object as value
  141. *
  142. * @param integer $order sort order
  143. * @param string $pattern pattern to match
  144. * @param boolean $negativeMatch should the pattern be inversed
  145. * @return array<\DirectoryIterator>
  146. */
  147. public function getFileObjects($order = SORT_ASC, $pattern = '', $negativeMatch = false) {
  148. // scan the folder
  149. $this->scanFileObjects();
  150. $objects = $this->fileObjects;
  151. // sort out non matching files
  152. if (!empty($pattern)) {
  153. foreach ($objects as $filename => $value) {
  154. if (((bool) preg_match($pattern, $filename)) == $negativeMatch) unset($objects[$filename]);
  155. }
  156. }
  157. if ($order == SORT_DESC) {
  158. krsort($objects, $order);
  159. }
  160. else if ($order == SORT_ASC) {
  161. ksort($objects, $order);
  162. }
  163. else if ($order == self::SORT_NONE) {
  164. // nothing to do here :)
  165. }
  166. else {
  167. throw new SystemException('The given sorting is not supported');
  168. }
  169. return $objects;
  170. }
  171. /**
  172. * Fills the list of available files
  173. */
  174. protected function scanFiles() {
  175. // value is cached
  176. if (!empty($this->files)) return;
  177. if ($this->recursive) {
  178. $it = new \RecursiveIteratorIterator($this->obj, \RecursiveIteratorIterator::CHILD_FIRST);
  179. foreach ($it as $filename => $obj) {
  180. // ignore . and ..
  181. if ($it->isDot()) continue;
  182. $this->files[FileUtil::unifyDirSeperator($filename)] = FileUtil::unifyDirSeperator($filename);
  183. }
  184. }
  185. else {
  186. foreach ($this->obj as $obj) {
  187. // ignore . and ..
  188. if ($this->obj->isDot()) continue;
  189. $this->files[FileUtil::unifyDirSeperator($obj->getFilename())] = FileUtil::unifyDirSeperator($obj->getFilename());
  190. }
  191. }
  192. // add the directory itself
  193. $this->files[$this->directory] = $this->directory;
  194. }
  195. /**
  196. * Fills the list of available files, with DirectoryIterator object as value
  197. */
  198. protected function scanFileObjects() {
  199. // value is cached
  200. if (!empty($this->fileObjects)) return;
  201. if ($this->recursive) {
  202. $it = new \RecursiveIteratorIterator($this->obj, \RecursiveIteratorIterator::CHILD_FIRST);
  203. foreach ($it as $filename => $obj) {
  204. // ignore . and ..
  205. if ($it->isDot()) continue;
  206. $this->fileObjects[FileUtil::unifyDirSeperator($filename)] = $obj;
  207. }
  208. }
  209. else {
  210. foreach ($this->obj as $obj) {
  211. // ignore . and ..
  212. if ($this->obj->isDot()) continue;
  213. $this->fileObjects[FileUtil::unifyDirSeperator($obj->getFilename())] = $obj;
  214. }
  215. }
  216. // add the directory itself
  217. $this->fileObjects[$this->directory] = new \SPLFileInfo($this->directory);
  218. }
  219. /**
  220. * Executes a callback on each file and returns false if callback is invalid.
  221. *
  222. * @param callback $callback valid callback
  223. * @param string $pattern callback is only applied to files matching the given pattern
  224. * @return boolean
  225. */
  226. public function executeCallback($callback, $pattern = '') {
  227. if (!is_callable($callback)) return false;
  228. $files = $this->getFileObjects(self::SORT_NONE, $pattern);
  229. foreach ($files as $filename => $obj) {
  230. call_user_func($callback, $filename, $obj);
  231. }
  232. return true;
  233. }
  234. /**
  235. * Recursive remove of directory.
  236. */
  237. public function removeAll() {
  238. $this->removePattern('');
  239. // destroy cached instance
  240. unset(static::$instances[$this->recursive][$this->directory]);
  241. }
  242. /**
  243. * Removes all files that match the given pattern.
  244. *
  245. * @param string $pattern pattern to match
  246. * @param boolean $negativeMatch should the pattern be inversed
  247. */
  248. public function removePattern($pattern, $negativeMatch = false) {
  249. if (!$this->recursive) throw new SystemException('Removing of files only works in recursive mode');
  250. $files = $this->getFileObjects(self::SORT_NONE, $pattern, $negativeMatch);
  251. foreach ($files as $filename => $obj) {
  252. if (!is_writable($obj->getPath())) {
  253. throw new SystemException("Could not remove directory: '".$obj->getPath()."' is not writable");
  254. }
  255. if ($obj->isDir()) {
  256. @rmdir($filename);
  257. }
  258. else if ($obj->isFile()) {
  259. unlink($filename);
  260. }
  261. }
  262. $this->clearCaches();
  263. }
  264. /**
  265. * Calculates the size of the directory.
  266. *
  267. * @return integer directory size in bytes
  268. */
  269. public function getSize() {
  270. if (!$this->recursive) throw new SystemException('Calculating of size only works in recursive mode');
  271. // read cached value first
  272. if ($this->size) return $this->size;
  273. $files = $this->getFileObjects(self::SORT_NONE);
  274. foreach ($files as $obj) {
  275. $this->size += $obj->getSize();
  276. }
  277. return $this->size;
  278. }
  279. /**
  280. * Clears the caches of the current instance
  281. */
  282. public function clearCaches() {
  283. // clear cached list of files
  284. $this->files = array();
  285. $this->fileObjects = array();
  286. // clear cached size
  287. $this->size = 0;
  288. }
  289. }