Skip to content

Commit 5b4b8e0

Browse files
committed
Add a object oriented FileStream
1 parent 5f5a1e9 commit 5b4b8e0

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed

src/FireFS/Streams/FileStream.php

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
<?php
2+
3+
/**
4+
* FireFS - Easily manage your filesystem, through PHP
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*
24+
* @category Library
25+
* @package FireFS
26+
* @author Axel Nana <[email protected]>
27+
* @copyright 2018 Aliens Group, Inc.
28+
* @license MIT <https://github.com/ElementaryFramework/FireFS/blob/master/LICENSE>
29+
* @version GIT: 0.0.1
30+
* @link http://firefs.na2axl.tk
31+
*/
32+
33+
namespace ElementaryFramework\FireFS\Streams;
34+
35+
use ElementaryFramework\Core\Events\WithEvents;
36+
use ElementaryFramework\Core\Streams\Events\StreamEvent;
37+
use ElementaryFramework\Core\Streams\IReadableStream;
38+
use ElementaryFramework\Core\Streams\ISeekableStream;
39+
use ElementaryFramework\Core\Streams\IWritableStream;
40+
use ElementaryFramework\FireFS\Exceptions\FileStreamException;
41+
42+
/**
43+
* File Stream
44+
*
45+
* Class used to read and write files through a stream for
46+
* async I/O under FireFS
47+
*
48+
* @package FireFS
49+
* @subpackage Streams
50+
* @author Axel Nana <[email protected]>
51+
*/
52+
class FileStream implements IReadableStream, IWritableStream, ISeekableStream
53+
{
54+
use WithEvents;
55+
56+
/**
57+
* Stream handle.
58+
*
59+
* @var resource
60+
*/
61+
private $_handle;
62+
63+
/**
64+
* The path to the file handled by this stream.
65+
*
66+
* @var string
67+
*/
68+
private $_path;
69+
70+
/**
71+
* The list of readable streams piped to this one.
72+
*
73+
* @var array
74+
*/
75+
private $_pipedStreams = array();
76+
77+
/**
78+
* Defines if the stream is closed or not.
79+
*
80+
* @var boolean
81+
*/
82+
private $_closed;
83+
84+
/**
85+
* Defines if the stream is paused or not.
86+
*
87+
* @var boolean
88+
*/
89+
private $_paused;
90+
91+
/**
92+
* Creates a new instance of the FileStream class.
93+
*
94+
* @param string $path The path to the file.
95+
* @param boolean $append Defines if the stream cursor start at
96+
* the end (true) or at the beginning (false)
97+
* of the file.
98+
*/
99+
public function __construct(string $path, bool $append)
100+
{
101+
if (($result = fopen($path, $append ? "a+" : "w+")) !== false) {
102+
$this->_handle = $result;
103+
} else {
104+
throw new FileStreamException("Unable to create the file stream.", $path);
105+
}
106+
107+
$this->_path = $path;
108+
$this->_closed = false;
109+
$this->_paused = false;
110+
111+
$this->on(
112+
StreamEvent::EVENT_PIPE,
113+
[$this, "_onPipe"]
114+
);
115+
}
116+
117+
private function _onPipe(IReadableStream $source)
118+
{
119+
$closure = \Closure::bind(function($data) {
120+
if (!$this->_paused) {
121+
$this->write($data);
122+
}
123+
}, $this);
124+
125+
$source->on(
126+
StreamEvent::EVENT_DATA,
127+
$closure
128+
);
129+
}
130+
131+
/**
132+
* @inheritDoc
133+
*/
134+
public function close() : void
135+
{
136+
if ($this->_closed)
137+
return;
138+
139+
if ($this->_closed = fclose($this->_handle)) {
140+
$this->emit(StreamEvent::EVENT_CLOSE);
141+
} else {
142+
$this->emit(
143+
StreamEvent::EVENT_ERROR,
144+
new FileStreamException("Unable to close the stream.", $this->_path)
145+
);
146+
}
147+
}
148+
149+
/**
150+
* @inheritDoc
151+
*/
152+
public function isReadable() : bool
153+
{
154+
return !$this->_closed;
155+
}
156+
157+
/**
158+
* @inheritDoc
159+
*/
160+
public function isWritable() : bool
161+
{
162+
return !$this->_closed;
163+
}
164+
165+
/**
166+
* @inheritDoc
167+
*/
168+
public function pause() : void
169+
{
170+
if ($this->_paused)
171+
return;
172+
173+
$this->_paused = true;
174+
}
175+
176+
/**
177+
* @inheritDoc
178+
*/
179+
public function resume() : void
180+
{
181+
if (!$this->_paused)
182+
return;
183+
184+
$this->_paused = false;
185+
}
186+
187+
/**
188+
* @inheritDoc
189+
*/
190+
public function pipe(IWritableStream $destination, bool $autoEnd = true) : IReadableStream
191+
{
192+
if (!$this->isReadable())
193+
return null;
194+
195+
if (!$destination->isWritable())
196+
$this->pause();
197+
198+
if ($autoEnd) {
199+
$this->on(
200+
StreamEvent::EVENT_END,
201+
function ($data) use (&$destination) {
202+
$destination->end($data);
203+
}
204+
);
205+
}
206+
207+
$destination->emit(
208+
StreamEvent::EVENT_PIPE,
209+
$this
210+
);
211+
212+
return $this;
213+
}
214+
215+
/**
216+
* @inheritDoc
217+
*/
218+
public function read(int $length) : void
219+
{
220+
if ($this->isReadable()) {
221+
$data = fread($this->_handle, $length);
222+
223+
if ($data === false) {
224+
$this->emit(
225+
StreamEvent::EVENT_ERROR,
226+
new FileStreamException("Unable to read the stream.", $this->_path)
227+
);
228+
} else {
229+
$this->emit(
230+
StreamEvent::EVENT_DATA,
231+
$data
232+
);
233+
}
234+
}
235+
}
236+
237+
/**
238+
* @inheritDoc
239+
*/
240+
public function write($data) : bool
241+
{
242+
if (!$this->isWritable())
243+
return false;
244+
245+
if (fwrite($this->_handle, $data) === false)
246+
return false;
247+
248+
return true;
249+
}
250+
251+
/**
252+
* @inheritDoc
253+
*/
254+
public function end($data = null) : void
255+
{
256+
if ($data !== null)
257+
$this->write($data);
258+
259+
$this->close();
260+
261+
$this->emit(
262+
StreamEvent::EVENT_END,
263+
$data
264+
);
265+
}
266+
267+
/**
268+
* @inheritDoc
269+
*/
270+
public function seekTo(int $offset, int $whence = SEEK_SET) : void
271+
{
272+
if (!$this->_closed) {
273+
fseek($this->_handle, $offset, $whence);
274+
}
275+
}
276+
277+
/**
278+
* @inheritDoc
279+
*/
280+
public function seekToStart() : void
281+
{
282+
$this->seekTo(0);
283+
}
284+
285+
/**
286+
* @inheritDoc
287+
*/
288+
public function seekToEnd() : void
289+
{
290+
$this->seekTo(0, SEEK_END);
291+
}
292+
293+
/**
294+
* Closes the stream when the instance is being destructed.
295+
*/
296+
function __destruct()
297+
{
298+
$this->close();
299+
}
300+
}

0 commit comments

Comments
 (0)