Passed
Push — master ( 54f366...417f46 )
by Blizzz
15:08 queued 13s
created

Quota::free_space()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 20
rs 8.8333
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author J0WI <[email protected]>
7
 * @author John Molakvoæ <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Julius Härtl <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Tigran Mkrtchyan <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
namespace OC\Files\Storage\Wrapper;
34
35
use OC\Files\Filesystem;
36
use OC\SystemConfig;
37
use OCP\Files\Cache\ICacheEntry;
38
use OCP\Files\FileInfo;
39
use OCP\Files\Storage\IStorage;
40
41
class Quota extends Wrapper {
42
	/** @var callable|null */
43
	protected $quotaCallback;
44
	protected ?int $quota;
45
	protected string $sizeRoot;
46
	private SystemConfig $config;
47
48
	/**
49
	 * @param array $parameters
50
	 */
51
	public function __construct($parameters) {
52
		parent::__construct($parameters);
53
		$this->quota = $parameters['quota'] ?? null;
54
		$this->quotaCallback = $parameters['quotaCallback'] ?? null;
55
		$this->sizeRoot = $parameters['root'] ?? '';
56
		$this->config = \OC::$server->get(SystemConfig::class);
57
	}
58
59
	/**
60
	 * @return int quota value
61
	 */
62
	public function getQuota(): int {
63
		if ($this->quota === null) {
64
			$quotaCallback = $this->quotaCallback;
65
			if ($quotaCallback === null) {
66
				throw new \Exception("No quota or quota callback provider");
67
			}
68
			$this->quota = $quotaCallback();
69
		}
70
		return $this->quota;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->quota could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
71
	}
72
73
	private function hasQuota(): bool {
74
		return $this->getQuota() !== FileInfo::SPACE_UNLIMITED;
75
	}
76
77
	/**
78
	 * @param string $path
79
	 * @param \OC\Files\Storage\Storage $storage
80
	 */
81
	protected function getSize($path, $storage = null) {
82
		if ($this->config->getValue('quota_include_external_storage', false)) {
83
			$rootInfo = Filesystem::getFileInfo('', 'ext');
0 ignored issues
show
Bug introduced by
'ext' of type string is incompatible with the type boolean expected by parameter $includeMountPoints of OC\Files\Filesystem::getFileInfo(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

83
			$rootInfo = Filesystem::getFileInfo('', /** @scrutinizer ignore-type */ 'ext');
Loading history...
84
			if ($rootInfo) {
85
				return $rootInfo->getSize(true);
86
			}
87
			return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
88
		} else {
89
			if (is_null($storage)) {
90
				$cache = $this->getCache();
91
			} else {
92
				$cache = $storage->getCache();
93
			}
94
			$data = $cache->get($path);
95
			if ($data instanceof ICacheEntry and isset($data['size'])) {
96
				return $data['size'];
97
			} else {
98
				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
99
			}
100
		}
101
	}
102
103
	/**
104
	 * Get free space as limited by the quota
105
	 *
106
	 * @param string $path
107
	 * @return int|bool
108
	 */
109
	public function free_space($path) {
110
		if (!$this->hasQuota()) {
111
			return $this->storage->free_space($path);
112
		}
113
		if ($this->getQuota() < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) {
114
			return $this->storage->free_space($path);
115
		} else {
116
			$used = $this->getSize($this->sizeRoot);
117
			if ($used < 0) {
118
				return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED;
119
			} else {
120
				$free = $this->storage->free_space($path);
121
				$quotaFree = max($this->getQuota() - $used, 0);
122
				// if free space is known
123
				if ($free >= 0) {
124
					$free = min($free, $quotaFree);
125
				} else {
126
					$free = $quotaFree;
127
				}
128
				return $free;
129
			}
130
		}
131
	}
132
133
	/**
134
	 * see https://www.php.net/manual/en/function.file_put_contents.php
135
	 *
136
	 * @param string $path
137
	 * @param mixed $data
138
	 * @return int|false
139
	 */
140
	public function file_put_contents($path, $data) {
141
		if (!$this->hasQuota()) {
142
			return $this->storage->file_put_contents($path, $data);
143
		}
144
		$free = $this->free_space($path);
145
		if ($free < 0 or strlen($data) < $free) {
146
			return $this->storage->file_put_contents($path, $data);
147
		} else {
148
			return false;
149
		}
150
	}
151
152
	/**
153
	 * see https://www.php.net/manual/en/function.copy.php
154
	 *
155
	 * @param string $source
156
	 * @param string $target
157
	 * @return bool
158
	 */
159
	public function copy($source, $target) {
160
		if (!$this->hasQuota()) {
161
			return $this->storage->copy($source, $target);
162
		}
163
		$free = $this->free_space($target);
164
		if ($free < 0 or $this->getSize($source) < $free) {
165
			return $this->storage->copy($source, $target);
166
		} else {
167
			return false;
168
		}
169
	}
170
171
	/**
172
	 * see https://www.php.net/manual/en/function.fopen.php
173
	 *
174
	 * @param string $path
175
	 * @param string $mode
176
	 * @return resource|bool
177
	 */
178
	public function fopen($path, $mode) {
179
		if (!$this->hasQuota()) {
180
			return $this->storage->fopen($path, $mode);
181
		}
182
		$source = $this->storage->fopen($path, $mode);
183
184
		// don't apply quota for part files
185
		if (!$this->isPartFile($path)) {
186
			$free = $this->free_space($path);
187
			if ($source && is_int($free) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') {
188
				// only apply quota for files, not metadata, trash or others
189
				if ($this->shouldApplyQuota($path)) {
190
					return \OC\Files\Stream\Quota::wrap($source, $free);
191
				}
192
			}
193
		}
194
		return $source;
195
	}
196
197
	/**
198
	 * Checks whether the given path is a part file
199
	 *
200
	 * @param string $path Path that may identify a .part file
201
	 * @return string File path without .part extension
202
	 * @note this is needed for reusing keys
203
	 */
204
	private function isPartFile($path) {
205
		$extension = pathinfo($path, PATHINFO_EXTENSION);
206
207
		return ($extension === 'part');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $extension === 'part' returns the type boolean which is incompatible with the documented return type string.
Loading history...
208
	}
209
210
	/**
211
	 * Only apply quota for files, not metadata, trash or others
212
	 */
213
	private function shouldApplyQuota(string $path): bool {
214
		return strpos(ltrim($path, '/'), 'files/') === 0;
215
	}
216
217
	/**
218
	 * @param IStorage $sourceStorage
219
	 * @param string $sourceInternalPath
220
	 * @param string $targetInternalPath
221
	 * @return bool
222
	 */
223
	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
224
		if (!$this->hasQuota()) {
225
			return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
226
		}
227
		$free = $this->free_space($targetInternalPath);
228
		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
229
			return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
230
		} else {
231
			return false;
232
		}
233
	}
234
235
	/**
236
	 * @param IStorage $sourceStorage
237
	 * @param string $sourceInternalPath
238
	 * @param string $targetInternalPath
239
	 * @return bool
240
	 */
241
	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
242
		if (!$this->hasQuota()) {
243
			return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
244
		}
245
		$free = $this->free_space($targetInternalPath);
246
		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
247
			return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
248
		} else {
249
			return false;
250
		}
251
	}
252
253
	public function mkdir($path) {
254
		if (!$this->hasQuota()) {
255
			return $this->storage->mkdir($path);
256
		}
257
		$free = $this->free_space($path);
258
		if ($this->shouldApplyQuota($path) && $free == 0) {
259
			return false;
260
		}
261
262
		return parent::mkdir($path);
263
	}
264
265
	public function touch($path, $mtime = null) {
266
		if (!$this->hasQuota()) {
267
			return $this->storage->touch($path, $mtime);
268
		}
269
		$free = $this->free_space($path);
270
		if ($free == 0) {
271
			return false;
272
		}
273
274
		return parent::touch($path, $mtime);
275
	}
276
}
277