Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
namespace GuzzleHttp\Psr7;
3
 
4
use Psr\Http\Message\StreamInterface;
5
 
6
/**
7
 * Reads from multiple streams, one after the other.
8
 *
9
 * This is a read-only stream decorator.
10
 */
11
class AppendStream implements StreamInterface
12
{
13
    /** @var StreamInterface[] Streams being decorated */
14
    private $streams = [];
15
 
16
    private $seekable = true;
17
    private $current = 0;
18
    private $pos = 0;
19
    private $detached = false;
20
 
21
    /**
22
     * @param StreamInterface[] $streams Streams to decorate. Each stream must
23
     *                                   be readable.
24
     */
25
    public function __construct(array $streams = [])
26
    {
27
        foreach ($streams as $stream) {
28
            $this->addStream($stream);
29
        }
30
    }
31
 
32
    public function __toString()
33
    {
34
        try {
35
            $this->rewind();
36
            return $this->getContents();
37
        } catch (\Exception $e) {
38
            return '';
39
        }
40
    }
41
 
42
    /**
43
     * Add a stream to the AppendStream
44
     *
45
     * @param StreamInterface $stream Stream to append. Must be readable.
46
     *
47
     * @throws \InvalidArgumentException if the stream is not readable
48
     */
49
    public function addStream(StreamInterface $stream)
50
    {
51
        if (!$stream->isReadable()) {
52
            throw new \InvalidArgumentException('Each stream must be readable');
53
        }
54
 
55
        // The stream is only seekable if all streams are seekable
56
        if (!$stream->isSeekable()) {
57
            $this->seekable = false;
58
        }
59
 
60
        $this->streams[] = $stream;
61
    }
62
 
63
    public function getContents()
64
    {
65
        return copy_to_string($this);
66
    }
67
 
68
    /**
69
     * Closes each attached stream.
70
     *
71
     * {@inheritdoc}
72
     */
73
    public function close()
74
    {
75
        $this->pos = $this->current = 0;
76
 
77
        foreach ($this->streams as $stream) {
78
            $stream->close();
79
        }
80
 
81
        $this->streams = [];
82
    }
83
 
84
    /**
85
     * Detaches each attached stream
86
     *
87
     * {@inheritdoc}
88
     */
89
    public function detach()
90
    {
91
        $this->close();
92
        $this->detached = true;
93
    }
94
 
95
    public function tell()
96
    {
97
        return $this->pos;
98
    }
99
 
100
    /**
101
     * Tries to calculate the size by adding the size of each stream.
102
     *
103
     * If any of the streams do not return a valid number, then the size of the
104
     * append stream cannot be determined and null is returned.
105
     *
106
     * {@inheritdoc}
107
     */
108
    public function getSize()
109
    {
110
        $size = 0;
111
 
112
        foreach ($this->streams as $stream) {
113
            $s = $stream->getSize();
114
            if ($s === null) {
115
                return null;
116
            }
117
            $size += $s;
118
        }
119
 
120
        return $size;
121
    }
122
 
123
    public function eof()
124
    {
125
        return !$this->streams ||
126
            ($this->current >= count($this->streams) - 1 &&
127
             $this->streams[$this->current]->eof());
128
    }
129
 
130
    public function rewind()
131
    {
132
        $this->seek(0);
133
    }
134
 
135
    /**
136
     * Attempts to seek to the given position. Only supports SEEK_SET.
137
     *
138
     * {@inheritdoc}
139
     */
140
    public function seek($offset, $whence = SEEK_SET)
141
    {
142
        if (!$this->seekable) {
143
            throw new \RuntimeException('This AppendStream is not seekable');
144
        } elseif ($whence !== SEEK_SET) {
145
            throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
146
        }
147
 
148
        $this->pos = $this->current = 0;
149
 
150
        // Rewind each stream
151
        foreach ($this->streams as $i => $stream) {
152
            try {
153
                $stream->rewind();
154
            } catch (\Exception $e) {
155
                throw new \RuntimeException('Unable to seek stream '
156
                    . $i . ' of the AppendStream', 0, $e);
157
            }
158
        }
159
 
160
        // Seek to the actual position by reading from each stream
161
        while ($this->pos < $offset && !$this->eof()) {
162
            $result = $this->read(min(8096, $offset - $this->pos));
163
            if ($result === '') {
164
                break;
165
            }
166
        }
167
    }
168
 
169
    /**
170
     * Reads from all of the appended streams until the length is met or EOF.
171
     *
172
     * {@inheritdoc}
173
     */
174
    public function read($length)
175
    {
176
        $buffer = '';
177
        $total = count($this->streams) - 1;
178
        $remaining = $length;
179
        $progressToNext = false;
180
 
181
        while ($remaining > 0) {
182
 
183
            // Progress to the next stream if needed.
184
            if ($progressToNext || $this->streams[$this->current]->eof()) {
185
                $progressToNext = false;
186
                if ($this->current === $total) {
187
                    break;
188
                }
189
                $this->current++;
190
            }
191
 
192
            $result = $this->streams[$this->current]->read($remaining);
193
 
194
            // Using a loose comparison here to match on '', false, and null
195
            if ($result == null) {
196
                $progressToNext = true;
197
                continue;
198
            }
199
 
200
            $buffer .= $result;
201
            $remaining = $length - strlen($buffer);
202
        }
203
 
204
        $this->pos += strlen($buffer);
205
 
206
        return $buffer;
207
    }
208
 
209
    public function isReadable()
210
    {
211
        return true;
212
    }
213
 
214
    public function isWritable()
215
    {
216
        return false;
217
    }
218
 
219
    public function isSeekable()
220
    {
221
        return $this->seekable;
222
    }
223
 
224
    public function write($string)
225
    {
226
        throw new \RuntimeException('Cannot write to an AppendStream');
227
    }
228
 
229
    public function getMetadata($key = null)
230
    {
231
        return $key ? null : [];
232
    }
233
}