Completed
Push — master ( 63676d...3faef6 )
by Lukas
28:10 queued 12:28
created

AssemblyStream::getNodeForPosition()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 3
nop 1
dl 0
loc 8
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Lukas Reschke <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 *
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OCA\DAV\Upload;
25
26
use Sabre\DAV\IFile;
27
28
/**
29
 * Class AssemblyStream
30
 *
31
 * The assembly stream is a virtual stream that wraps multiple chunks.
32
 * Reading from the stream transparently accessed the underlying chunks and
33
 * give a representation as if they were already merged together.
34
 *
35
 * @package OCA\DAV\Upload
36
 */
37
class AssemblyStream implements \Icewind\Streams\File {
38
39
	/** @var resource */
40
	private $context;
41
42
	/** @var IFile[] */
43
	private $nodes;
44
45
	/** @var int */
46
	private $pos = 0;
47
48
	/** @var int */
49
	private $size = 0;
50
51
	/** @var resource */
52
	private $currentStream = null;
53
54
	/** @var int */
55
	private $currentNode = 0;
56
57
	/**
58
	 * @param string $path
59
	 * @param string $mode
60
	 * @param int $options
61
	 * @param string &$opened_path
62
	 * @return bool
63
	 */
64
	public function stream_open($path, $mode, $options, &$opened_path) {
65
		$this->loadContext('assembly');
66
67
		$nodes = $this->nodes;
68
		// http://stackoverflow.com/a/10985500
69
		@usort($nodes, function (IFile $a, IFile $b) {
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
70
			return strnatcmp($a->getName(), $b->getName());
71
		});
72
		$this->nodes = array_values($nodes);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_values($nodes) of type array<integer,?> is incompatible with the declared type array<integer,object<Sabre\DAV\IFile>> of property $nodes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
73
		if (count($this->nodes) > 0) {
74
			$this->currentStream = $this->getStream($this->nodes[0]);
75
		}
76
		$this->size = array_reduce($this->nodes, function ($size, IFile $file) {
77
			return $size + $file->getSize();
78
		}, 0);
79
		return true;
80
	}
81
82
	/**
83
	 * @param string $offset
84
	 * @param int $whence
85
	 * @return bool
86
	 */
87
	public function stream_seek($offset, $whence = SEEK_SET) {
88
		return false;
89
	}
90
91
	/**
92
	 * @return int
93
	 */
94
	public function stream_tell() {
95
		return $this->pos;
96
	}
97
98
	/**
99
	 * @param int $count
100
	 * @return string
101
	 */
102
	public function stream_read($count) {
103
		if (is_null($this->currentStream)) {
104
			return '';
105
		}
106
107
		do {
108
			$data = fread($this->currentStream, $count);
109
			$read = strlen($data);
110
111
			if (feof($this->currentStream)) {
112
				fclose($this->currentStream);
113
				$this->currentNode++;
114
				if ($this->currentNode < count($this->nodes)) {
115
					$this->currentStream = $this->getStream($this->nodes[$this->currentNode]);
116
				} else {
117
					$this->currentStream = null;
118
				}
119
			}
120
			// if no data read, try again with the next node because
121
			// returning empty data can make the caller think there is no more
122
			// data left to read
123
		} while ($read === 0 && !is_null($this->currentStream));
124
125
		// update position
126
		$this->pos += $read;
127
		return $data;
128
	}
129
130
	/**
131
	 * @param string $data
132
	 * @return int
133
	 */
134
	public function stream_write($data) {
135
		return false;
136
	}
137
138
	/**
139
	 * @param int $option
140
	 * @param int $arg1
141
	 * @param int $arg2
142
	 * @return bool
143
	 */
144
	public function stream_set_option($option, $arg1, $arg2) {
145
		return false;
146
	}
147
148
	/**
149
	 * @param int $size
150
	 * @return bool
151
	 */
152
	public function stream_truncate($size) {
153
		return false;
154
	}
155
156
	/**
157
	 * @return array
158
	 */
159
	public function stream_stat() {
160
		return [];
161
	}
162
163
	/**
164
	 * @param int $operation
165
	 * @return bool
166
	 */
167
	public function stream_lock($operation) {
168
		return false;
169
	}
170
171
	/**
172
	 * @return bool
173
	 */
174
	public function stream_flush() {
175
		return false;
176
	}
177
178
	/**
179
	 * @return bool
180
	 */
181
	public function stream_eof() {
182
		return $this->pos >= $this->size;
183
	}
184
185
	/**
186
	 * @return bool
187
	 */
188
	public function stream_close() {
189
		return true;
190
	}
191
192
193
	/**
194
	 * Load the source from the stream context and return the context options
195
	 *
196
	 * @param string $name
197
	 * @return array
198
	 * @throws \Exception
199
	 */
200
	protected function loadContext($name) {
201
		$context = stream_context_get_options($this->context);
202
		if (isset($context[$name])) {
203
			$context = $context[$name];
204
		} else {
205
			throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
206
		}
207
		if (isset($context['nodes']) and is_array($context['nodes'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
208
			$this->nodes = $context['nodes'];
209
		} else {
210
			throw new \BadMethodCallException('Invalid context, nodes not set');
211
		}
212
		return $context;
213
	}
214
215
	/**
216
	 * @param IFile[] $nodes
217
	 * @return resource
218
	 *
219
	 * @throws \BadMethodCallException
220
	 */
221
	public static function wrap(array $nodes) {
222
		$context = stream_context_create([
223
			'assembly' => [
224
				'nodes' => $nodes
225
			]
226
		]);
227
		stream_wrapper_register('assembly', self::class);
228
		try {
229
			$wrapped = fopen('assembly://', 'r', null, $context);
230
		} catch (\BadMethodCallException $e) {
231
			stream_wrapper_unregister('assembly');
232
			throw $e;
233
		}
234
		stream_wrapper_unregister('assembly');
235
		return $wrapped;
236
	}
237
238
	/**
239
	 * @param IFile $node
240
	 * @return resource
241
	 */
242 View Code Duplication
	private function getStream(IFile $node) {
243
		$data = $node->get();
244
		if (is_resource($data)) {
245
			return $data;
246
		} else {
247
			$tmp = fopen('php://temp', 'w+');
248
			fwrite($tmp, $data);
249
			rewind($tmp);
250
			return $tmp;
251
		}
252
	}
253
}
254