Passed
Push — master ( 0485e6...d8dea5 )
by Morris
14:45 queued 13s
created

ObjectStoreStorage::getObjectStore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Marcel Klehr <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC\Files\ObjectStore;
31
32
use Icewind\Streams\CallbackWrapper;
33
use Icewind\Streams\CountWrapper;
34
use Icewind\Streams\IteratorDirectory;
35
use OC\Files\Cache\CacheEntry;
36
use OCP\Files\NotFoundException;
37
use OCP\Files\ObjectStore\IObjectStore;
38
39
class ObjectStoreStorage extends \OC\Files\Storage\Common {
40
	/**
41
	 * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
42
	 */
43
	protected $objectStore;
44
	/**
45
	 * @var string $id
46
	 */
47
	protected $id;
48
	/**
49
	 * @var \OC\User\User $user
50
	 */
51
	protected $user;
52
53
	private $objectPrefix = 'urn:oid:';
54
55
	private $logger;
56
57
	public function __construct($params) {
58
		if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
59
			$this->objectStore = $params['objectstore'];
60
		} else {
61
			throw new \Exception('missing IObjectStore instance');
62
		}
63
		if (isset($params['storageid'])) {
64
			$this->id = 'object::store:' . $params['storageid'];
65
		} else {
66
			$this->id = 'object::store:' . $this->objectStore->getStorageId();
67
		}
68
		if (isset($params['objectPrefix'])) {
69
			$this->objectPrefix = $params['objectPrefix'];
70
		}
71
		//initialize cache with root directory in cache
72
		if (!$this->is_dir('/')) {
73
			$this->mkdir('/');
74
		}
75
76
		$this->logger = \OC::$server->getLogger();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

76
		$this->logger = /** @scrutinizer ignore-deprecated */ \OC::$server->getLogger();
Loading history...
77
	}
78
79
	public function mkdir($path) {
80
		$path = $this->normalizePath($path);
81
82
		if ($this->file_exists($path)) {
83
			return false;
84
		}
85
86
		$mTime = time();
87
		$data = [
88
			'mimetype' => 'httpd/unix-directory',
89
			'size' => 0,
90
			'mtime' => $mTime,
91
			'storage_mtime' => $mTime,
92
			'permissions' => \OCP\Constants::PERMISSION_ALL,
93
		];
94
		if ($path === '') {
95
			//create root on the fly
96
			$data['etag'] = $this->getETag('');
97
			$this->getCache()->put('', $data);
98
			return true;
99
		} else {
100
			// if parent does not exist, create it
101
			$parent = $this->normalizePath(dirname($path));
102
			$parentType = $this->filetype($parent);
103
			if ($parentType === false) {
104
				if (!$this->mkdir($parent)) {
105
					// something went wrong
106
					return false;
107
				}
108
			} elseif ($parentType === 'file') {
109
				// parent is a file
110
				return false;
111
			}
112
			// finally create the new dir
113
			$mTime = time(); // update mtime
114
			$data['mtime'] = $mTime;
115
			$data['storage_mtime'] = $mTime;
116
			$data['etag'] = $this->getETag($path);
117
			$this->getCache()->put($path, $data);
118
			return true;
119
		}
120
	}
121
122
	/**
123
	 * @param string $path
124
	 * @return string
125
	 */
126
	private function normalizePath($path) {
127
		$path = trim($path, '/');
128
		//FIXME why do we sometimes get a path like 'files//username'?
129
		$path = str_replace('//', '/', $path);
130
131
		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
132
		if (!$path || $path === '.') {
133
			$path = '';
134
		}
135
136
		return $path;
137
	}
138
139
	/**
140
	 * Object Stores use a NoopScanner because metadata is directly stored in
141
	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
142
	 *
143
	 * @param string $path
144
	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
0 ignored issues
show
Bug introduced by
The type OC\Files\ObjectStore\optional was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
145
	 * @return \OC\Files\ObjectStore\NoopScanner
146
	 */
147
	public function getScanner($path = '', $storage = null) {
148
		if (!$storage) {
149
			$storage = $this;
150
		}
151
		if (!isset($this->scanner)) {
152
			$this->scanner = new NoopScanner($storage);
153
		}
154
		return $this->scanner;
155
	}
156
157
	public function getId() {
158
		return $this->id;
159
	}
160
161
	public function rmdir($path) {
162
		$path = $this->normalizePath($path);
163
164
		if (!$this->is_dir($path)) {
165
			return false;
166
		}
167
168
		if (!$this->rmObjects($path)) {
169
			return false;
170
		}
171
172
		$this->getCache()->remove($path);
173
174
		return true;
175
	}
176
177
	private function rmObjects($path) {
178
		$children = $this->getCache()->getFolderContents($path);
179
		foreach ($children as $child) {
180
			if ($child['mimetype'] === 'httpd/unix-directory') {
181
				if (!$this->rmObjects($child['path'])) {
182
					return false;
183
				}
184
			} else {
185
				if (!$this->unlink($child['path'])) {
186
					return false;
187
				}
188
			}
189
		}
190
191
		return true;
192
	}
193
194
	public function unlink($path) {
195
		$path = $this->normalizePath($path);
196
		$stat = $this->stat($path);
197
198
		if ($stat && isset($stat['fileid'])) {
199
			if ($stat['mimetype'] === 'httpd/unix-directory') {
200
				return $this->rmdir($path);
201
			}
202
			try {
203
				$this->objectStore->deleteObject($this->getURN($stat['fileid']));
204
			} catch (\Exception $ex) {
205
				if ($ex->getCode() !== 404) {
206
					$this->logger->logException($ex, [
207
						'app' => 'objectstore',
208
						'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
209
					]);
210
					return false;
211
				}
212
				//removing from cache is ok as it does not exist in the objectstore anyway
213
			}
214
			$this->getCache()->remove($path);
215
			return true;
216
		}
217
		return false;
218
	}
219
220
	public function stat($path) {
221
		$path = $this->normalizePath($path);
222
		$cacheEntry = $this->getCache()->get($path);
223
		if ($cacheEntry instanceof CacheEntry) {
224
			return $cacheEntry->getData();
225
		} else {
226
			return false;
227
		}
228
	}
229
230
	public function getPermissions($path) {
231
		$stat = $this->stat($path);
232
233
		if (is_array($stat) && isset($stat['permissions'])) {
234
			return $stat['permissions'];
235
		}
236
237
		return parent::getPermissions($path);
238
	}
239
240
	/**
241
	 * Override this method if you need a different unique resource identifier for your object storage implementation.
242
	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
243
	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
244
	 *
245
	 * @param int $fileId the fileid
246
	 * @return null|string the unified resource name used to identify the object
247
	 */
248
	public function getURN($fileId) {
249
		if (is_numeric($fileId)) {
0 ignored issues
show
introduced by
The condition is_numeric($fileId) is always true.
Loading history...
250
			return $this->objectPrefix . $fileId;
251
		}
252
		return null;
253
	}
254
255
	public function opendir($path) {
256
		$path = $this->normalizePath($path);
257
258
		try {
259
			$files = [];
260
			$folderContents = $this->getCache()->getFolderContents($path);
261
			foreach ($folderContents as $file) {
262
				$files[] = $file['name'];
263
			}
264
265
			return IteratorDirectory::wrap($files);
266
		} catch (\Exception $e) {
267
			$this->logger->logException($e);
268
			return false;
269
		}
270
	}
271
272
	public function filetype($path) {
273
		$path = $this->normalizePath($path);
274
		$stat = $this->stat($path);
275
		if ($stat) {
276
			if ($stat['mimetype'] === 'httpd/unix-directory') {
277
				return 'dir';
278
			}
279
			return 'file';
280
		} else {
281
			return false;
282
		}
283
	}
284
285
	public function fopen($path, $mode) {
286
		$path = $this->normalizePath($path);
287
288
		if (strrpos($path, '.') !== false) {
289
			$ext = substr($path, strrpos($path, '.'));
290
		} else {
291
			$ext = '';
292
		}
293
294
		switch ($mode) {
295
			case 'r':
296
			case 'rb':
297
				$stat = $this->stat($path);
298
				if (is_array($stat)) {
299
					try {
300
						return $this->objectStore->readObject($this->getURN($stat['fileid']));
301
					} catch (NotFoundException $e) {
302
						$this->logger->logException($e, [
303
							'app' => 'objectstore',
304
							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
305
						]);
306
						throw $e;
307
					} catch (\Exception $ex) {
308
						$this->logger->logException($ex, [
309
							'app' => 'objectstore',
310
							'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
311
						]);
312
						return false;
313
					}
314
				} else {
315
					return false;
316
				}
317
				// no break
318
			case 'w':
319
			case 'wb':
320
			case 'w+':
321
			case 'wb+':
322
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getTempManager() has been deprecated. ( Ignorable by Annotation )

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

322
				$tmpFile = /** @scrutinizer ignore-deprecated */ \OC::$server->getTempManager()->getTemporaryFile($ext);
Loading history...
323
				$handle = fopen($tmpFile, $mode);
324
				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
325
					$this->writeBack($tmpFile, $path);
326
				});
327
			case 'a':
328
			case 'ab':
329
			case 'r+':
330
			case 'a+':
331
			case 'x':
332
			case 'x+':
333
			case 'c':
334
			case 'c+':
335
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getTempManager() has been deprecated. ( Ignorable by Annotation )

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

335
				$tmpFile = /** @scrutinizer ignore-deprecated */ \OC::$server->getTempManager()->getTemporaryFile($ext);
Loading history...
336
				if ($this->file_exists($path)) {
337
					$source = $this->fopen($path, 'r');
338
					file_put_contents($tmpFile, $source);
339
				}
340
				$handle = fopen($tmpFile, $mode);
341
				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
342
					$this->writeBack($tmpFile, $path);
343
				});
344
		}
345
		return false;
346
	}
347
348
	public function file_exists($path) {
349
		$path = $this->normalizePath($path);
350
		return (bool)$this->stat($path);
351
	}
352
353
	public function rename($source, $target) {
354
		$source = $this->normalizePath($source);
355
		$target = $this->normalizePath($target);
356
		$this->remove($target);
357
		$this->getCache()->move($source, $target);
358
		$this->touch(dirname($target));
359
		return true;
360
	}
361
362
	public function getMimeType($path) {
363
		$path = $this->normalizePath($path);
364
		return parent::getMimeType($path);
365
	}
366
367
	public function touch($path, $mtime = null) {
368
		if (is_null($mtime)) {
369
			$mtime = time();
370
		}
371
372
		$path = $this->normalizePath($path);
373
		$dirName = dirname($path);
374
		$parentExists = $this->is_dir($dirName);
375
		if (!$parentExists) {
376
			return false;
377
		}
378
379
		$stat = $this->stat($path);
380
		if (is_array($stat)) {
381
			// update existing mtime in db
382
			$stat['mtime'] = $mtime;
383
			$this->getCache()->update($stat['fileid'], $stat);
384
		} else {
385
			try {
386
				//create a empty file, need to have at least on char to make it
387
				// work with all object storage implementations
388
				$this->file_put_contents($path, ' ');
389
				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getMimeTypeDetector() has been deprecated. ( Ignorable by Annotation )

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

389
				$mimeType = /** @scrutinizer ignore-deprecated */ \OC::$server->getMimeTypeDetector()->detectPath($path);
Loading history...
390
				$stat = [
391
					'etag' => $this->getETag($path),
392
					'mimetype' => $mimeType,
393
					'size' => 0,
394
					'mtime' => $mtime,
395
					'storage_mtime' => $mtime,
396
					'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
397
				];
398
				$this->getCache()->put($path, $stat);
399
			} catch (\Exception $ex) {
400
				$this->logger->logException($ex, [
401
					'app' => 'objectstore',
402
					'message' => 'Could not create object for ' . $path,
403
				]);
404
				throw $ex;
405
			}
406
		}
407
		return true;
408
	}
409
410
	public function writeBack($tmpFile, $path) {
411
		$size = filesize($tmpFile);
412
		$this->writeStream($path, fopen($tmpFile, 'r'), $size);
413
	}
414
415
	/**
416
	 * external changes are not supported, exclusive access to the object storage is assumed
417
	 *
418
	 * @param string $path
419
	 * @param int $time
420
	 * @return false
421
	 */
422
	public function hasUpdated($path, $time) {
423
		return false;
424
	}
425
426
	public function needsPartFile() {
427
		return false;
428
	}
429
430
	public function file_put_contents($path, $data) {
431
		$handle = $this->fopen($path, 'w+');
432
		fwrite($handle, $data);
433
		fclose($handle);
434
		return true;
435
	}
436
437
	public function writeStream(string $path, $stream, int $size = null): int {
438
		$stat = $this->stat($path);
439
		if (empty($stat)) {
440
			// create new file
441
			$stat = [
442
				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
443
			];
444
		}
445
		// update stat with new data
446
		$mTime = time();
447
		$stat['size'] = (int)$size;
448
		$stat['mtime'] = $mTime;
449
		$stat['storage_mtime'] = $mTime;
450
451
		$mimetypeDetector = \OC::$server->getMimeTypeDetector();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getMimeTypeDetector() has been deprecated. ( Ignorable by Annotation )

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

451
		$mimetypeDetector = /** @scrutinizer ignore-deprecated */ \OC::$server->getMimeTypeDetector();
Loading history...
452
		$mimetype = $mimetypeDetector->detectPath($path);
453
454
		$stat['mimetype'] = $mimetype;
455
		$stat['etag'] = $this->getETag($path);
456
457
		$exists = $this->getCache()->inCache($path);
458
		$uploadPath = $exists ? $path : $path . '.part';
459
460
		if ($exists) {
461
			$fileId = $stat['fileid'];
462
		} else {
463
			$fileId = $this->getCache()->put($uploadPath, $stat);
464
		}
465
466
		$urn = $this->getURN($fileId);
467
		try {
468
			//upload to object storage
469
			if ($size === null) {
470
				$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
471
					$this->getCache()->update($fileId, [
472
						'size' => $writtenSize
473
					]);
474
					$size = $writtenSize;
475
				});
476
				$this->objectStore->writeObject($urn, $countStream);
477
				if (is_resource($countStream)) {
478
					fclose($countStream);
479
				}
480
				$stat['size'] = $size;
481
			} else {
482
				$this->objectStore->writeObject($urn, $stream);
483
			}
484
		} catch (\Exception $ex) {
485
			if (!$exists) {
486
				/*
487
				 * Only remove the entry if we are dealing with a new file.
488
				 * Else people lose access to existing files
489
				 */
490
				$this->getCache()->remove($uploadPath);
491
				$this->logger->logException($ex, [
492
					'app' => 'objectstore',
493
					'message' => 'Could not create object ' . $urn . ' for ' . $path,
494
				]);
495
			} else {
496
				$this->logger->logException($ex, [
497
					'app' => 'objectstore',
498
					'message' => 'Could not update object ' . $urn . ' for ' . $path,
499
				]);
500
			}
501
			throw $ex; // make this bubble up
502
		}
503
504
		if ($exists) {
505
			$this->getCache()->update($fileId, $stat);
506
		} else {
507
			if ($this->objectStore->objectExists($urn)) {
508
				$this->getCache()->move($uploadPath, $path);
509
			} else {
510
				$this->getCache()->remove($uploadPath);
511
				throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404);
512
			}
513
		}
514
515
		return $size;
516
	}
517
518
	public function getObjectStore(): IObjectStore {
519
		return $this->objectStore;
520
	}
521
}
522