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\Consistency;
40use Hoa\Stream;
41
42/**
43 * Class \Hoa\File.
44 *
45 * File handler.
46 *
47 * @copyright  Copyright © 2007-2017 Hoa community
48 * @license    New BSD License
49 */
50abstract class File
51    extends    Generic
52    implements Stream\IStream\Bufferable,
53               Stream\IStream\Lockable,
54               Stream\IStream\Pointable
55{
56    /**
57     * Open for reading only; place the file pointer at the beginning of the
58     * file.
59     *
60     * @const string
61     */
62    const MODE_READ                = 'rb';
63
64    /**
65     * Open for reading and writing; place the file pointer at the beginning of
66     * the file.
67     *
68     * @const string
69     */
70    const MODE_READ_WRITE          = 'r+b';
71
72    /**
73     * Open for writing only; place the file pointer at the beginning of the
74     * file and truncate the file to zero length. If the file does not exist,
75     * attempt to create it.
76     *
77     * @const string
78     */
79    const MODE_TRUNCATE_WRITE      = 'wb';
80
81    /**
82     * Open for reading and writing; place the file pointer at the beginning of
83     * the file and truncate the file to zero length. If the file does not
84     * exist, attempt to create it.
85     *
86     * @const string
87     */
88    const MODE_TRUNCATE_READ_WRITE = 'w+b';
89
90    /**
91     * Open for writing only; place the file pointer at the end of the file. If
92     * the file does not exist, attempt to create it.
93     *
94     * @const string
95     */
96    const MODE_APPEND_WRITE        = 'ab';
97
98    /**
99     * Open for reading and writing; place the file pointer at the end of the
100     * file. If the file does not exist, attempt to create it.
101     *
102     * @const string
103     */
104    const MODE_APPEND_READ_WRITE   = 'a+b';
105
106    /**
107     * Create and open for writing only; place the file pointer at the beginning
108     * of the file. If the file already exits, the fopen() call with fail by
109     * returning false and generating an error of level E_WARNING. If the file
110     * does not exist, attempt to create it. This is equivalent to specifying
111     * O_EXCL | O_CREAT flags for the underlying open(2) system call.
112     *
113     * @const string
114     */
115    const MODE_CREATE_WRITE        = 'xb';
116
117    /**
118     * Create and open for reading and writing; place the file pointer at the
119     * beginning of the file. If the file already exists, the fopen() call with
120     * fail by returning false and generating an error of level E_WARNING. If
121     * the file does not exist, attempt to create it. This is equivalent to
122     * specifying O_EXCL | O_CREAT flags for the underlying open(2) system call.
123     *
124     * @const string
125     */
126    const MODE_CREATE_READ_WRITE   = 'x+b';
127
128
129
130    /**
131     * Open a file.
132     *
133     * @param   string  $streamName    Stream name (or file descriptor).
134     * @param   string  $mode          Open mode, see the self::MODE_*
135     *                                 constants.
136     * @param   string  $context       Context ID (please, see the
137     *                                 \Hoa\Stream\Context class).
138     * @param   bool    $wait          Differ opening or not.
139     * @throws  \Hoa\File\Exception
140     */
141    public function __construct(
142        $streamName,
143        $mode,
144        $context = null,
145        $wait    = false
146    ) {
147        $this->setMode($mode);
148
149        switch ($streamName) {
150
151            case '0':
152                $streamName = 'php://stdin';
153
154                break;
155
156            case '1':
157                $streamName = 'php://stdout';
158
159                break;
160
161            case '2':
162                $streamName = 'php://stderr';
163
164                break;
165
166            default:
167                if (true === ctype_digit($streamName)) {
168                    if (PHP_VERSION_ID >= 50306) {
169                        $streamName = 'php://fd/' . $streamName;
170                    } else {
171                        throw new Exception(
172                            'You need PHP5.3.6 to use a file descriptor ' .
173                            'other than 0, 1 or 2 (tried %d with PHP%s).',
174                            0,
175                            [$streamName, PHP_VERSION]
176                        );
177                    }
178                }
179        }
180
181        parent::__construct($streamName, $context, $wait);
182
183        return;
184    }
185
186    /**
187     * Open the stream and return the associated resource.
188     *
189     * @param   string               $streamName    Stream name (e.g. path or URL).
190     * @param   \Hoa\Stream\Context  $context       Context.
191     * @return  resource
192     * @throws  \Hoa\File\Exception\FileDoesNotExist
193     * @throws  \Hoa\File\Exception
194     */
195    protected function &_open($streamName, Stream\Context $context = null)
196    {
197        if (substr($streamName, 0, 4) == 'file' &&
198            false === is_dir(dirname($streamName))) {
199            throw new Exception(
200                'Directory %s does not exist. Could not open file %s.',
201                1,
202                [dirname($streamName), basename($streamName)]
203            );
204        }
205
206        if (null === $context) {
207            if (false === $out = @fopen($streamName, $this->getMode(), true)) {
208                throw new Exception(
209                    'Failed to open stream %s.',
210                    2,
211                    $streamName
212                );
213            }
214
215            return $out;
216        }
217
218        $out = @fopen(
219            $streamName,
220            $this->getMode(),
221            true,
222            $context->getContext()
223        );
224
225        if (false === $out) {
226            throw new Exception(
227                'Failed to open stream %s.',
228                3,
229                $streamName
230            );
231        }
232
233        return $out;
234    }
235
236    /**
237     * Close the current stream.
238     *
239     * @return  bool
240     */
241    protected function _close()
242    {
243        return @fclose($this->getStream());
244    }
245
246    /**
247     * Start a new buffer.
248     * The callable acts like a light filter.
249     *
250     * @param   mixed   $callable    Callable.
251     * @param   int     $size        Size.
252     * @return  int
253     */
254    public function newBuffer($callable = null, $size = null)
255    {
256        $this->setStreamBuffer($size);
257
258        //@TODO manage $callable as a filter?
259
260        return 1;
261    }
262
263    /**
264     * Flush the output to a stream.
265     *
266     * @return  bool
267     */
268    public function flush()
269    {
270        return fflush($this->getStream());
271    }
272
273    /**
274     * Delete buffer.
275     *
276     * @return  bool
277     */
278    public function deleteBuffer()
279    {
280        return $this->disableStreamBuffer();
281    }
282
283    /**
284     * Get bufffer level.
285     *
286     * @return  int
287     */
288    public function getBufferLevel()
289    {
290        return 1;
291    }
292
293    /**
294     * Get buffer size.
295     *
296     * @return  int
297     */
298    public function getBufferSize()
299    {
300        return $this->getStreamBufferSize();
301    }
302
303    /**
304     * Portable advisory locking.
305     *
306     * @param   int     $operation    Operation, use the
307     *                                \Hoa\Stream\IStream\Lockable::LOCK_* constants.
308     * @return  bool
309     */
310    public function lock($operation)
311    {
312        return flock($this->getStream(), $operation);
313    }
314
315    /**
316     * Rewind the position of a stream pointer.
317     *
318     * @return  bool
319     */
320    public function rewind()
321    {
322        return rewind($this->getStream());
323    }
324
325    /**
326     * Seek on a stream pointer.
327     *
328     * @param   int     $offset    Offset (negative value should be supported).
329     * @param   int     $whence    Whence, use the
330     *                             \Hoa\Stream\IStream\Pointable::SEEK_* constants.
331     * @return  int
332     */
333    public function seek($offset, $whence = Stream\IStream\Pointable::SEEK_SET)
334    {
335        return fseek($this->getStream(), $offset, $whence);
336    }
337
338    /**
339     * Get the current position of the stream pointer.
340     *
341     * @return  int
342     */
343    public function tell()
344    {
345        $stream = $this->getStream();
346
347        if (null === $stream) {
348            return 0;
349        }
350
351        return ftell($stream);
352    }
353
354    /**
355     * Create a file.
356     *
357     * @param   string  $name     File name.
358     * @param   mixed   $dummy    To be compatible with childs.
359     * @return  bool
360     */
361    public static function create($name, $dummy)
362    {
363        if (file_exists($name)) {
364            return true;
365        }
366
367        return touch($name);
368    }
369}
370
371/**
372 * Flex entity.
373 */
374Consistency::flexEntity('Hoa\File\File');
375