1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\File; 38 39use Hoa\Stream; 40 41/** 42 * Class \Hoa\File\Directory. 43 * 44 * Directory handler. 45 * 46 * @copyright Copyright © 2007-2017 Hoa community 47 * @license New BSD License 48 */ 49class Directory extends Generic 50{ 51 /** 52 * Open for reading. 53 * 54 * @const string 55 */ 56 const MODE_READ = 'rb'; 57 58 /** 59 * Open for reading and writing. If the directory does not exist, attempt to 60 * create it. 61 * 62 * @const string 63 */ 64 const MODE_CREATE = 'xb'; 65 66 /** 67 * Open for reading and writing. If the directory does not exist, attempt to 68 * create it recursively. 69 * 70 * @const string 71 */ 72 const MODE_CREATE_RECURSIVE = 'xrb'; 73 74 75 76 /** 77 * Open a directory. 78 * 79 * @param string $streamName Stream name. 80 * @param string $mode Open mode, see the self::MODE* constants. 81 * @param string $context Context ID (please, see the 82 * \Hoa\Stream\Context class). 83 * @param bool $wait Differ opening or not. 84 */ 85 public function __construct( 86 $streamName, 87 $mode = self::MODE_READ, 88 $context = null, 89 $wait = false 90 ) { 91 $this->setMode($mode); 92 parent::__construct($streamName, $context, $wait); 93 94 return; 95 } 96 97 /** 98 * Open the stream and return the associated resource. 99 * 100 * @param string $streamName Stream name (e.g. path or URL). 101 * @param \Hoa\Stream\Context $context Context. 102 * @return resource 103 * @throws \Hoa\File\Exception\FileDoesNotExist 104 * @throws \Hoa\File\Exception 105 */ 106 protected function &_open($streamName, Stream\Context $context = null) 107 { 108 if (false === is_dir($streamName)) { 109 if ($this->getMode() == self::MODE_READ) { 110 throw new Exception\FileDoesNotExist( 111 'Directory %s does not exist.', 112 0, 113 $streamName 114 ); 115 } else { 116 self::create( 117 $streamName, 118 $this->getMode(), 119 null !== $context 120 ? $context->getContext() 121 : null 122 ); 123 } 124 } 125 126 $out = null; 127 128 return $out; 129 } 130 131 /** 132 * Close the current stream. 133 * 134 * @return bool 135 */ 136 protected function _close() 137 { 138 return true; 139 } 140 141 /** 142 * Recursive copy of a directory. 143 * 144 * @param string $to Destination path. 145 * @param bool $force Force to copy if the file $to already exists. 146 * Use the \Hoa\Stream\IStream\Touchable::*OVERWRITE 147 * constants. 148 * @return bool 149 * @throws \Hoa\File\Exception 150 */ 151 public function copy($to, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE) 152 { 153 if (empty($to)) { 154 throw new Exception( 155 'The destination path (to copy) is empty.', 156 1 157 ); 158 } 159 160 $from = $this->getStreamName(); 161 $fromLength = strlen($from) + 1; 162 $finder = new Finder(); 163 $finder->in($from); 164 165 self::create($to, self::MODE_CREATE_RECURSIVE); 166 167 foreach ($finder as $file) { 168 $relative = substr($file->getPathname(), $fromLength); 169 $_to = $to . DS . $relative; 170 171 if (true === $file->isDir()) { 172 self::create($_to, self::MODE_CREATE); 173 174 continue; 175 } 176 177 // This is not possible to do `$file->open()->copy(); 178 // $file->close();` because the file will be opened in read and 179 // write mode. In a PHAR for instance, this operation is 180 // forbidden. So a special care must be taken to open file in read 181 // only mode. 182 $handle = null; 183 184 if (true === $file->isFile()) { 185 $handle = new Read($file->getPathname()); 186 } elseif (true === $file->isDir()) { 187 $handle = new Directory($file->getPathName()); 188 } elseif (true === $file->isLink()) { 189 $handle = new Link\Read($file->getPathName()); 190 } 191 192 if (null !== $handle) { 193 $handle->copy($_to, $force); 194 $handle->close(); 195 } 196 } 197 198 return true; 199 } 200 201 /** 202 * Delete a directory. 203 * 204 * @return bool 205 */ 206 public function delete() 207 { 208 $from = $this->getStreamName(); 209 $finder = new Finder(); 210 $finder->in($from) 211 ->childFirst(); 212 213 foreach ($finder as $file) { 214 $file->open()->delete(); 215 $file->close(); 216 } 217 218 if (null === $this->getStreamContext()) { 219 return @rmdir($from); 220 } 221 222 return @rmdir($from, $this->getStreamContext()->getContext()); 223 } 224 225 /** 226 * Create a directory. 227 * 228 * @param string $name Directory name. 229 * @param string $mode Create mode. Please, see the self::MODE_CREATE* 230 * constants. 231 * @param string $context Context ID (please, see the 232 * \Hoa\Stream\Context class). 233 * @return bool 234 * @throws \Hoa\File\Exception 235 */ 236 public static function create( 237 $name, 238 $mode = self::MODE_CREATE_RECURSIVE, 239 $context = null 240 ) { 241 if (true === is_dir($name)) { 242 return true; 243 } 244 245 if (empty($name)) { 246 return false; 247 } 248 249 if (null !== $context) { 250 if (false === Stream\Context::contextExists($context)) { 251 throw new Exception( 252 'Context %s was not previously declared, cannot retrieve ' . 253 'this context.', 254 2, 255 $context 256 ); 257 } else { 258 $context = Stream\Context::getInstance($context); 259 } 260 } 261 262 if (null === $context) { 263 return @mkdir( 264 $name, 265 0755, 266 self::MODE_CREATE_RECURSIVE === $mode 267 ); 268 } 269 270 return @mkdir( 271 $name, 272 0755, 273 self::MODE_CREATE_RECURSIVE === $mode, 274 $context->getContext() 275 ); 276 } 277} 278