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