Completed
Push — master ( ec6e3a...a32d5d )
by Thomas
10:38
created

AssemblyStream::stream_close()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Lukas Reschke <[email protected]>
4
 * @author Markus Goetz <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 * @author Vincent Petry <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2018, ownCloud GmbH
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
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
	protected $context;
41
42
	/** @var IFile[] */
43
	protected $nodes;
44
45
	/** @var int */
46
	protected $pos = 0;
47
48
	/** @var array */
49
	protected $sortedNodes;
50
51
	/** @var int */
52
	protected $size;
53
54
	/** @var resource */
55
	protected $currentStream = null;
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
		// sort the nodes
68
		$nodes = $this->nodes;
69
		// http://stackoverflow.com/a/10985500
70
		@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...
71
			return strnatcmp($a->getName(), $b->getName());
72
		});
73
		$this->nodes = $nodes;
74
75
		// build additional information
76
		$this->sortedNodes = [];
77
		$start = 0;
78
		foreach($this->nodes as $node) {
79
			$size = $node->getSize();
80
			$name = $node->getName();
81
			// ignore .zsync metadata file
82
			if (!strcmp($name,".zsync"))
83
				continue;
84
			$this->sortedNodes[$name] = ['node' => $node, 'start' => $start, 'end' => $start + $size];
85
			$start += $size;
86
			$this->size = $start;
87
		}
88
		return true;
89
	}
90
91
	/**
92
	 * @param string $offset
93
	 * @param int $whence
94
	 * @return bool
95
	 */
96
	public function stream_seek($offset, $whence = SEEK_SET) {
97
		return false;
98
	}
99
100
	/**
101
	 * @return int
102
	 */
103
	public function stream_tell() {
104
		return $this->pos;
105
	}
106
107
	/**
108
	 * @param int $count
109
	 * @return string
110
	 */
111
	public function stream_read($count) {
112
		do {
113
			if ($this->currentStream === null) {
114
				list($node, $posInNode) = $this->getNodeForPosition($this->pos);
115
				if (is_null($node)) {
116
					// reached last node, no more data
117
					return '';
118
				}
119
				$this->currentStream = $this->getStream($node);
120
				fseek($this->currentStream, $posInNode);
121
			}
122
123
			$data = fread($this->currentStream, $count);
124
			// isset is faster than strlen
125 View Code Duplication
			if (isset($data[$count - 1])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
				// we read the full count
127
				$read = $count;
128
			} else {
129
				// reaching end of stream, which happens less often so strlen is ok
130
				$read = strlen($data);
131
			}
132
133
			if (feof($this->currentStream)) {
134
				fclose($this->currentStream);
135
				$this->currentNode = null;
0 ignored issues
show
Bug introduced by
The property currentNode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
136
				$this->currentStream = null;
137
			}
138
			// if no data read, try again with the next node because
139
			// returning empty data can make the caller think there is no more
140
			// data left to read
141
		} while ($read === 0);
142
143
		// update position
144
		$this->pos += $read;
145
		return $data;
146
	}
147
148
	/**
149
	 * @param string $data
150
	 * @return int
151
	 */
152
	public function stream_write($data) {
153
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Icewind\Streams\File::stream_write of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
154
	}
155
156
	/**
157
	 * @param int $option
158
	 * @param int $arg1
159
	 * @param int $arg2
160
	 * @return bool
161
	 */
162
	public function stream_set_option($option, $arg1, $arg2) {
163
		return false;
164
	}
165
166
	/**
167
	 * @param int $size
168
	 * @return bool
169
	 */
170
	public function stream_truncate($size) {
171
		return false;
172
	}
173
174
	/**
175
	 * @return array
176
	 */
177
	public function stream_stat() {
178
		return [];
179
	}
180
181
	/**
182
	 * @param int $operation
183
	 * @return bool
184
	 */
185
	public function stream_lock($operation) {
186
		return false;
187
	}
188
189
	/**
190
	 * @return bool
191
	 */
192
	public function stream_flush() {
193
		return false;
194
	}
195
196
	/**
197
	 * @return bool
198
	 */
199
	public function stream_eof() {
200
		return $this->pos >= $this->size;
201
	}
202
203
	/**
204
	 * @return bool
205
	 */
206
	public function stream_close() {
207
		return true;
208
	}
209
210
211
	/**
212
	 * Load the source from the stream context and return the context options
213
	 *
214
	 * @param string $name
215
	 * @return array
216
	 * @throws \Exception
217
	 */
218
	protected function loadContext($name) {
219
		$context = stream_context_get_options($this->context);
220 View Code Duplication
		if (isset($context[$name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
			$context = $context[$name];
222
		} else {
223
			throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
224
		}
225 View Code Duplication
		if (isset($context['nodes']) and is_array($context['nodes'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
			$this->nodes = $context['nodes'];
227
		} else {
228
			throw new \BadMethodCallException('Invalid context, nodes not set');
229
		}
230
		return $context;
231
	}
232
233
	/**
234
	 * @param IFile[] $nodes
235
	 * @return resource
236
	 *
237
	 * @throws \BadMethodCallException
238
	 */
239
	public static function wrap(array $nodes, IFile $backingFile = null, $length = null) {
240
		$context = stream_context_create([
241
			'assembly' => [
242
				'nodes' => $nodes]
243
		]);
244
		stream_wrapper_register('assembly', '\OCA\DAV\Upload\AssemblyStream');
245
		try {
246
			$wrapped = fopen('assembly://', 'r', null, $context);
247
		} catch (\BadMethodCallException $e) {
248
			stream_wrapper_unregister('assembly');
249
			throw $e;
250
		}
251
		stream_wrapper_unregister('assembly');
252
		return $wrapped;
253
	}
254
255
	/**
256
	 * @param $pos
257
	 * @return IFile | null
258
	 */
259
	protected function getNodeForPosition($pos) {
260
		foreach($this->sortedNodes as $node) {
261
			if ($pos >= $node['start'] && $pos < $node['end']) {
262
				return [$node['node'], $pos - $node['start']];
263
			}
264
		}
265
		return null;
266
	}
267
268
	/**
269
	 * @param IFile $node
270
	 * @return resource
271
	 */
272
	protected function getStream(IFile $node) {
273
		$data = $node->get();
274
		if (is_resource($data)) {
275
			return $data;
276
		}
277
278
		return fopen('data://text/plain,' . $data,'r');
279
	}
280
281
}
282