Completed
Pull Request — master (#4890)
by Blizzz
18:15
created

ObjectStoreStorage::stat()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
64
		}
65
		//initialize cache with root directory in cache
66
		if (!$this->is_dir('/')) {
67
			$this->mkdir('/');
68
		}
69
70
		$this->logger = \OC::$server->getLogger();
71
	}
72
73
	public function mkdir($path) {
74
		$path = $this->normalizePath($path);
75
76
		if ($this->file_exists($path)) {
77
			return false;
78
		}
79
80
		$mTime = time();
81
		$data = [
82
			'mimetype' => 'httpd/unix-directory',
83
			'size' => 0,
84
			'mtime' => $mTime,
85
			'storage_mtime' => $mTime,
86
			'permissions' => \OCP\Constants::PERMISSION_ALL,
87
		];
88
		if ($path === '') {
89
			//create root on the fly
90
			$data['etag'] = $this->getETag('');
91
			$this->getCache()->put('', $data);
92
			return true;
93
		} else {
94
			// if parent does not exist, create it
95
			$parent = $this->normalizePath(dirname($path));
96
			$parentType = $this->filetype($parent);
97
			if ($parentType === false) {
98
				if (!$this->mkdir($parent)) {
99
					// something went wrong
100
					return false;
101
				}
102
			} else if ($parentType === 'file') {
103
				// parent is a file
104
				return false;
105
			}
106
			// finally create the new dir
107
			$mTime = time(); // update mtime
108
			$data['mtime'] = $mTime;
109
			$data['storage_mtime'] = $mTime;
110
			$data['etag'] = $this->getETag($path);
111
			$this->getCache()->put($path, $data);
112
			return true;
113
		}
114
	}
115
116
	/**
117
	 * @param string $path
118
	 * @return string
119
	 */
120 View Code Duplication
	private function normalizePath($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
121
		$path = trim($path, '/');
122
		//FIXME why do we sometimes get a path like 'files//username'?
123
		$path = str_replace('//', '/', $path);
124
125
		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
126
		if (!$path || $path === '.') {
127
			$path = '';
128
		}
129
130
		return $path;
131
	}
132
133
	/**
134
	 * Object Stores use a NoopScanner because metadata is directly stored in
135
	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
136
	 *
137
	 * @param string $path
138
	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
139
	 * @return \OC\Files\ObjectStore\NoopScanner
140
	 */
141 View Code Duplication
	public function getScanner($path = '', $storage = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
142
		if (!$storage) {
143
			$storage = $this;
144
		}
145
		if (!isset($this->scanner)) {
146
			$this->scanner = new NoopScanner($storage);
147
		}
148
		return $this->scanner;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->scanner; of type OC\Files\ObjectStore\Noo...haring\External\Scanner adds the type OCA\Files_Sharing\External\Scanner to the return on line 148 which is incompatible with the return type documented by OC\Files\ObjectStore\Obj...toreStorage::getScanner of type OC\Files\ObjectStore\NoopScanner.
Loading history...
149
	}
150
151
	public function getId() {
152
		return $this->id;
153
	}
154
155
	public function rmdir($path) {
156
		$path = $this->normalizePath($path);
157
158
		if (!$this->is_dir($path)) {
159
			return false;
160
		}
161
162
		$this->rmObjects($path);
163
164
		$this->getCache()->remove($path);
165
166
		return true;
167
	}
168
169
	private function rmObjects($path) {
170
		$children = $this->getCache()->getFolderContents($path);
171
		foreach ($children as $child) {
172
			if ($child['mimetype'] === 'httpd/unix-directory') {
173
				$this->rmObjects($child['path']);
174
			} else {
175
				$this->unlink($child['path']);
176
			}
177
		}
178
	}
179
180
	public function unlink($path) {
181
		$path = $this->normalizePath($path);
182
		$stat = $this->stat($path);
183
184
		if ($stat && isset($stat['fileid'])) {
185
			if ($stat['mimetype'] === 'httpd/unix-directory') {
186
				return $this->rmdir($path);
187
			}
188
			try {
189
				$this->objectStore->deleteObject($this->getURN($stat['fileid']));
190
			} catch (\Exception $ex) {
191
				if ($ex->getCode() !== 404) {
192
					$this->logger->logException($ex, [
193
						'app' => 'objectstore',
194
						'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
195
					]);
196
					return false;
197
				} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
198
					//removing from cache is ok as it does not exist in the objectstore anyway
199
				}
200
			}
201
			$this->getCache()->remove($path);
202
			return true;
203
		}
204
		return false;
205
	}
206
207
	public function stat($path) {
208
		$path = $this->normalizePath($path);
209
		$cacheEntry = $this->getCache()->get($path);
210
		if ($cacheEntry instanceof CacheEntry) {
211
			return $cacheEntry->getData();
212
		} else {
213
			return false;
214
		}
215
	}
216
217
	/**
218
	 * Override this method if you need a different unique resource identifier for your object storage implementation.
219
	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
220
	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
221
	 *
222
	 * @param int $fileId the fileid
223
	 * @return null|string the unified resource name used to identify the object
224
	 */
225
	protected function getURN($fileId) {
226
		if (is_numeric($fileId)) {
227
			return $this->objectPrefix . $fileId;
228
		}
229
		return null;
230
	}
231
232
	public function opendir($path) {
233
		$path = $this->normalizePath($path);
234
235
		try {
236
			$files = array();
237
			$folderContents = $this->getCache()->getFolderContents($path);
238
			foreach ($folderContents as $file) {
239
				$files[] = $file['name'];
240
			}
241
242
			return IteratorDirectory::wrap($files);
243
		} catch (\Exception $e) {
244
			$this->logger->logException($e);
245
			return false;
246
		}
247
	}
248
249
	public function filetype($path) {
250
		$path = $this->normalizePath($path);
251
		$stat = $this->stat($path);
252
		if ($stat) {
253
			if ($stat['mimetype'] === 'httpd/unix-directory') {
254
				return 'dir';
255
			}
256
			return 'file';
257
		} else {
258
			return false;
259
		}
260
	}
261
262
	public function fopen($path, $mode) {
263
		$path = $this->normalizePath($path);
264
265
		switch ($mode) {
266
			case 'r':
267
			case 'rb':
268
				$stat = $this->stat($path);
269
				if (is_array($stat)) {
270
					try {
271
						return $this->objectStore->readObject($this->getURN($stat['fileid']));
272
					} catch (\Exception $ex) {
273
						$this->logger->logException($ex, [
274
							'app' => 'objectstore',
275
							'message' => 'Count not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
276
						]);
277
						return false;
278
					}
279
				} else {
280
					return false;
281
				}
282
			case 'w':
283
			case 'wb':
284
			case 'a':
285
			case 'ab':
286
			case 'r+':
287
			case 'w+':
288
			case 'wb+':
289
			case 'a+':
290
			case 'x':
291
			case 'x+':
292
			case 'c':
293 View Code Duplication
			case 'c+':
294
				if (strrpos($path, '.') !== false) {
295
					$ext = substr($path, strrpos($path, '.'));
296
				} else {
297
					$ext = '';
298
				}
299
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
300
				if ($this->file_exists($path)) {
301
					$source = $this->fopen($path, 'r');
302
					file_put_contents($tmpFile, $source);
303
				}
304
				$handle = fopen($tmpFile, $mode);
305
				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
306
					$this->writeBack($tmpFile, $path);
307
				});
308
		}
309
		return false;
310
	}
311
312
	public function file_exists($path) {
313
		$path = $this->normalizePath($path);
314
		return (bool)$this->stat($path);
315
	}
316
317
	public function rename($source, $target) {
318
		$source = $this->normalizePath($source);
319
		$target = $this->normalizePath($target);
320
		$this->remove($target);
321
		$this->getCache()->move($source, $target);
322
		$this->touch(dirname($target));
323
		return true;
324
	}
325
326
	public function getMimeType($path) {
327
		$path = $this->normalizePath($path);
328
		$stat = $this->stat($path);
329
		if (is_array($stat)) {
330
			return $stat['mimetype'];
331
		} else {
332
			return false;
333
		}
334
	}
335
336
	public function touch($path, $mtime = null) {
337
		if (is_null($mtime)) {
338
			$mtime = time();
339
		}
340
341
		$path = $this->normalizePath($path);
342
		$dirName = dirname($path);
343
		$parentExists = $this->is_dir($dirName);
344
		if (!$parentExists) {
345
			return false;
346
		}
347
348
		$stat = $this->stat($path);
349
		if (is_array($stat)) {
350
			// update existing mtime in db
351
			$stat['mtime'] = $mtime;
352
			$this->getCache()->update($stat['fileid'], $stat);
353
		} else {
354
			$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
355
			// create new file
356
			$stat = array(
357
				'etag' => $this->getETag($path),
358
				'mimetype' => $mimeType,
359
				'size' => 0,
360
				'mtime' => $mtime,
361
				'storage_mtime' => $mtime,
362
				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
363
			);
364
			$fileId = $this->getCache()->put($path, $stat);
365
			try {
366
				//read an empty file from memory
367
				$this->objectStore->writeObject($this->getURN($fileId), fopen('php://memory', 'r'));
368
			} catch (\Exception $ex) {
369
				$this->getCache()->remove($path);
370
				$this->logger->logException($ex, [
371
					'app' => 'objectstore',
372
					'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
373
				]);
374
				return false;
375
			}
376
		}
377
		return true;
378
	}
379
380
	public function writeBack($tmpFile, $path) {
381
		$stat = $this->stat($path);
382
		if (empty($stat)) {
383
			// create new file
384
			$stat = array(
385
				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
386
			);
387
		}
388
		// update stat with new data
389
		$mTime = time();
390
		$stat['size'] = filesize($tmpFile);
391
		$stat['mtime'] = $mTime;
392
		$stat['storage_mtime'] = $mTime;
393
		$stat['mimetype'] = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
394
		$stat['etag'] = $this->getETag($path);
395
396
		$fileId = $this->getCache()->put($path, $stat);
397
		try {
398
			//upload to object storage
399
			$this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r'));
400
		} catch (\Exception $ex) {
401
			$this->getCache()->remove($path);
402
			$this->logger->logException($ex, [
403
				'app' => 'objectstore',
404
				'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
405
			]);
406
			throw $ex; // make this bubble up
407
		}
408
	}
409
410
	/**
411
	 * external changes are not supported, exclusive access to the object storage is assumed
412
	 *
413
	 * @param string $path
414
	 * @param int $time
415
	 * @return false
416
	 */
417
	public function hasUpdated($path, $time) {
418
		return false;
419
	}
420
}
421