Completed
Push — master ( 24a68b...bc84ac )
by Thomas
22:37 queued 09:54
created

Common::getLocalFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author hkjolhede <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Martin Mattel <[email protected]>
11
 * @author Michael Gapczynski <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Robin McCorkell <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Sam Tuke <[email protected]>
17
 * @author scambra <[email protected]>
18
 * @author Stefan Weil <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 *
22
 * @copyright Copyright (c) 2018, ownCloud GmbH
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
39
namespace OC\Files\Storage;
40
41
use OC\Files\Cache\Cache;
42
use OC\Files\Cache\Propagator;
43
use OC\Files\Cache\Scanner;
44
use OC\Files\Cache\Updater;
45
use OC\Files\Filesystem;
46
use OC\Files\Cache\Watcher;
47
use OC\Lock\Persistent\Lock;
48
use OC\Lock\Persistent\LockManager;
49
use OCP\Constants;
50
use OCP\Files\FileInfo;
51
use OCP\Files\FileNameTooLongException;
52
use OCP\Files\InvalidCharacterInPathException;
53
use OCP\Files\InvalidPathException;
54
use OCP\Files\ReservedWordException;
55
use OCP\Files\Storage\ILockingStorage;
56
use OCP\Files\Storage\IStorage;
57
use OCP\Files\Storage\IPersistentLockingStorage;
58
use OCP\Files\Storage\IVersionedStorage;
59
use OCP\Lock\ILockingProvider;
60
61
/**
62
 * Storage backend class for providing common filesystem operation methods
63
 * which are not storage-backend specific.
64
 *
65
 * \OC\Files\Storage\Common is never used directly; it is extended by all other
66
 * storage backends, where its methods may be overridden, and additional
67
 * (backend-specific) methods are defined.
68
 *
69
 * Some \OC\Files\Storage\Common methods call functions which are first defined
70
 * in classes which extend it, e.g. $this->stat() .
71
 */
72
abstract class Common implements Storage, ILockingStorage, IVersionedStorage, IPersistentLockingStorage {
73
	use LocalTempFileTrait;
74
75
	protected $cache;
76
	protected $scanner;
77
	protected $watcher;
78
	protected $propagator;
79
	protected $storageCache;
80
	protected $updater;
81
82
	protected $mountOptions = [];
83
	protected $owner = null;
84
85
	public function __construct($parameters) {
86
	}
87
88
	/**
89
	 * Remove a file or folder
90
	 *
91
	 * @param string $path
92
	 * @return bool
93
	 */
94 View Code Duplication
	protected function remove($path) {
95
		if ($this->is_dir($path)) {
96
			return $this->rmdir($path);
97
		} elseif ($this->is_file($path)) {
98
			return $this->unlink($path);
99
		} else {
100
			return false;
101
		}
102
	}
103
104
	public function is_dir($path) {
105
		return $this->filetype($path) === 'dir';
106
	}
107
108
	public function is_file($path) {
109
		return $this->filetype($path) === 'file';
110
	}
111
112
	public function filesize($path) {
113
		if ($this->is_dir($path)) {
114
			return 0; //by definition
115
		} else {
116
			$stat = $this->stat($path);
117
			if (isset($stat['size'])) {
118
				return $stat['size'];
119
			} else {
120
				return 0;
121
			}
122
		}
123
	}
124
125
	public function isReadable($path) {
126
		// at least check whether it exists
127
		// subclasses might want to implement this more thoroughly
128
		return $this->file_exists($path);
129
	}
130
131
	public function isUpdatable($path) {
132
		// at least check whether it exists
133
		// subclasses might want to implement this more thoroughly
134
		// a non-existing file/folder isn't updatable
135
		return $this->file_exists($path);
136
	}
137
138
	public function isCreatable($path) {
139
		if ($this->is_dir($path) && $this->isUpdatable($path)) {
140
			return true;
141
		}
142
		return false;
143
	}
144
145
	public function isDeletable($path) {
146
		if ($path === '' || $path === '/') {
147
			return false;
148
		}
149
		$parent = \dirname($path);
150
		return $this->isUpdatable($parent) && $this->isUpdatable($path);
151
	}
152
153
	public function isSharable($path) {
154
		return $this->isReadable($path);
155
	}
156
157
	public function getPermissions($path) {
158
		$permissions = 0;
159
		if ($this->isCreatable($path)) {
160
			$permissions |= Constants::PERMISSION_CREATE;
161
		}
162
		if ($this->isReadable($path)) {
163
			$permissions |= Constants::PERMISSION_READ;
164
		}
165
		if ($this->isUpdatable($path)) {
166
			$permissions |= Constants::PERMISSION_UPDATE;
167
		}
168
		if ($this->isDeletable($path)) {
169
			$permissions |= Constants::PERMISSION_DELETE;
170
		}
171
		if ($this->isSharable($path)) {
172
			$permissions |= Constants::PERMISSION_SHARE;
173
		}
174
		return $permissions;
175
	}
176
177
	public function filemtime($path) {
178
		$stat = $this->stat($path);
179
		if (isset($stat['mtime'])) {
180
			return $stat['mtime'];
181
		} else {
182
			return 0;
183
		}
184
	}
185
186
	public function file_get_contents($path) {
187
		$handle = $this->fopen($path, "r");
188
		if (!$handle) {
189
			return false;
190
		}
191
		$data = \stream_get_contents($handle);
192
		\fclose($handle);
193
		return $data;
194
	}
195
196
	public function file_put_contents($path, $data) {
197
		$handle = $this->fopen($path, "w");
198
		$this->removeCachedFile($path);
199
		$count = \fwrite($handle, $data);
200
		\fclose($handle);
201
		return $count;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $count; (integer) is incompatible with the return type declared by the interface OCP\Files\Storage\IStorage::file_put_contents of type boolean.

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...
202
	}
203
204
	public function rename($path1, $path2) {
205
		$this->remove($path2);
206
207
		$this->removeCachedFile($path1);
208
		return $this->copy($path1, $path2) and $this->remove($path1);
209
	}
210
211
	public function copy($path1, $path2) {
212
		if ($this->is_dir($path1)) {
213
			$this->remove($path2);
214
			$dir = $this->opendir($path1);
215
			$this->mkdir($path2);
216
			while ($file = \readdir($dir)) {
217
				if (!Filesystem::isIgnoredDir($file) && !Filesystem::isForbiddenFileOrDir($file)) {
218
					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
219
						return false;
220
					}
221
				}
222
			}
223
			\closedir($dir);
224
			return true;
225
		} else {
226
			$source = $this->fopen($path1, 'r');
227
			$target = $this->fopen($path2, 'w');
228
			list(, $result) = \OC_Helper::streamCopy($source, $target);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC_Helper::streamCopy() does only seem to accept resource, did you maybe forget to handle an error condition?
Loading history...
Security Bug introduced by
It seems like $target can also be of type false; however, OC_Helper::streamCopy() does only seem to accept resource, did you maybe forget to handle an error condition?
Loading history...
229
			$this->removeCachedFile($path2);
230
			return $result;
231
		}
232
	}
233
234
	public function getMimeType($path) {
235
		if ($this->is_dir($path)) {
236
			return 'httpd/unix-directory';
237
		} elseif ($this->file_exists($path)) {
238
			return \OC::$server->getMimeTypeDetector()->detectPath($path);
239
		} else {
240
			return false;
241
		}
242
	}
243
244 View Code Duplication
	public function hash($type, $path, $raw = false) {
245
		$fh = $this->fopen($path, 'rb');
246
		$ctx = \hash_init($type);
247
		\hash_update_stream($ctx, $fh);
248
		\fclose($fh);
249
		return \hash_final($ctx, $raw);
250
	}
251
252
	public function search($query) {
253
		return $this->searchInDir($query);
254
	}
255
256
	public function getLocalFile($path) {
257
		return $this->getCachedFile($path);
258
	}
259
260
	/**
261
	 * @param string $path
262
	 * @param string $target
263
	 */
264
	private function addLocalFolder($path, $target) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
265
		$dh = $this->opendir($path);
266
		if (\is_resource($dh)) {
267
			while (($file = \readdir($dh)) !== false) {
268
				if (!Filesystem::isIgnoredDir($file)) {
269
					if ($this->is_dir($path . '/' . $file)) {
270
						\mkdir($target . '/' . $file);
271
						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
272
					} else {
273
						$tmp = $this->toTmpFile($path . '/' . $file);
274
						\rename($tmp, $target . '/' . $file);
275
					}
276
				}
277
			}
278
		}
279
	}
280
281
	/**
282
	 * @param string $query
283
	 * @param string $dir
284
	 * @return array
285
	 */
286
	protected function searchInDir($query, $dir = '') {
287
		$files = [];
288
		$dh = $this->opendir($dir);
289
		if (\is_resource($dh)) {
290
			while (($item = \readdir($dh)) !== false) {
291
				if (Filesystem::isIgnoredDir($item)) {
292
					continue;
293
				}
294 View Code Duplication
				if (\strstr(\strtolower($item), \strtolower($query)) !== false) {
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...
295
					$files[] = $dir . '/' . $item;
296
				}
297
				if ($this->is_dir($dir . '/' . $item)) {
298
					$files = \array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
299
				}
300
			}
301
		}
302
		\closedir($dh);
303
		return $files;
304
	}
305
306
	/**
307
	 * check if a file or folder has been updated since $time
308
	 *
309
	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
310
	 * the mtime should always return false here. As a result storage implementations that always return false expect
311
	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
312
	 * ownClouds filesystem.
313
	 *
314
	 * @param string $path
315
	 * @param int $time
316
	 * @return bool
317
	 */
318
	public function hasUpdated($path, $time) {
319
		return $this->filemtime($path) > $time;
320
	}
321
322
	public function getCache($path = '', $storage = null) {
323
		if (!$storage) {
324
			$storage = $this;
325
		}
326
		if (!isset($storage->cache)) {
327
			$storage->cache = new Cache($storage);
328
		}
329
		return $storage->cache;
330
	}
331
332
	public function getScanner($path = '', $storage = null) {
333
		if (!$storage) {
334
			$storage = $this;
335
		}
336
		if (!isset($storage->scanner)) {
337
			$storage->scanner = new Scanner($storage);
338
		}
339
		return $storage->scanner;
340
	}
341
342
	public function getWatcher($path = '', $storage = null) {
343
		if (!$storage) {
344
			$storage = $this;
345
		}
346
		if (!isset($this->watcher)) {
347
			$this->watcher = new Watcher($storage);
348
			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
349
			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
350
		}
351
		return $this->watcher;
352
	}
353
354
	/**
355
	 * get a propagator instance for the cache
356
	 *
357
	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
358
	 * @return \OC\Files\Cache\Propagator
359
	 */
360
	public function getPropagator($storage = null) {
361
		if (!$storage) {
362
			$storage = $this;
363
		}
364
		if (!isset($storage->propagator)) {
365
			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
366
		}
367
		return $storage->propagator;
368
	}
369
370
	public function getUpdater($storage = null) {
371
		if (!$storage) {
372
			$storage = $this;
373
		}
374
		if (!isset($storage->updater)) {
375
			$storage->updater = new Updater($storage);
376
		}
377
		return $storage->updater;
378
	}
379
380
	public function getStorageCache($storage = null) {
381
		if (!$storage) {
382
			$storage = $this;
383
		}
384
		if (!isset($this->storageCache)) {
385
			$this->storageCache = new \OC\Files\Cache\Storage($storage);
386
		}
387
		return $this->storageCache;
388
	}
389
390
	/**
391
	 * get the owner of a path
392
	 *
393
	 * @param string $path The path to get the owner
394
	 * @return string|false uid or false
395
	 */
396
	public function getOwner($path) {
397
		if ($this->owner === null) {
398
			$this->owner = \OC_User::getUser();
399
		}
400
401
		return $this->owner;
402
	}
403
404
	/**
405
	 * get the ETag for a file or folder
406
	 *
407
	 * @param string $path
408
	 * @return string
409
	 */
410
	public function getETag($path) {
411
		return \uniqid();
412
	}
413
414
	/**
415
	 * clean a path, i.e. remove all redundant '.' and '..'
416
	 * making sure that it can't point to higher than '/'
417
	 *
418
	 * @param string $path The path to clean
419
	 * @return string cleaned path
420
	 */
421
	public function cleanPath($path) {
422
		if (\strlen($path) == 0 or $path[0] != '/') {
423
			$path = '/' . $path;
424
		}
425
426
		$output = [];
427
		foreach (\explode('/', $path) as $chunk) {
428
			if ($chunk == '..') {
429
				\array_pop($output);
430
			} elseif ($chunk == '.') {
431
			} else {
432
				$output[] = $chunk;
433
			}
434
		}
435
		return \implode('/', $output);
436
	}
437
438
	/**
439
	 * Test a storage for availability
440
	 *
441
	 * @return bool
442
	 */
443
	public function test() {
444
		if ($this->stat('')) {
445
			return true;
446
		}
447
		return false;
448
	}
449
450
	/**
451
	 * get the free space in the storage
452
	 *
453
	 * @param string $path
454
	 * @return int|false
455
	 */
456
	public function free_space($path) {
457
		return FileInfo::SPACE_UNKNOWN;
458
	}
459
460
	/**
461
	 * {@inheritdoc}
462
	 */
463
	public function isLocal() {
464
		// the common implementation returns a temporary file by
465
		// default, which is not local
466
		return false;
467
	}
468
469
	/**
470
	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
471
	 *
472
	 * @param string $class
473
	 * @return bool
474
	 */
475
	public function instanceOfStorage($class) {
476
		if (\ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
477
			// FIXME Temporary fix to keep existing checks working
478
			$class = '\OCA\Files_Sharing\SharedStorage';
479
		}
480
		return \is_a($this, $class);
481
	}
482
483
	/**
484
	 * A custom storage implementation can return an url for direct download of a give file.
485
	 *
486
	 * For now the returned array can hold the parameter url - in future more attributes might follow.
487
	 *
488
	 * @param string $path
489
	 * @return array|false
490
	 */
491
	public function getDirectDownload($path) {
492
		return [];
493
	}
494
495
	/**
496
	 * @inheritdoc
497
	 */
498
	public function verifyPath($path, $fileName) {
499
		if (isset($fileName[255])) {
500
			throw new FileNameTooLongException();
501
		}
502
503
		$this->verifyPosixPath($fileName);
504
	}
505
506
	/**
507
	 * @param string $fileName
508
	 * @throws InvalidPathException
509
	 */
510
	protected function verifyPosixPath($fileName) {
511
		$fileName = \trim($fileName);
512
		$this->scanForInvalidCharacters($fileName, "\\/");
513
		$reservedNames = ['*'];
514
		if (\in_array($fileName, $reservedNames)) {
515
			throw new ReservedWordException();
516
		}
517
	}
518
519
	/**
520
	 * @param string $fileName
521
	 * @param string $invalidChars
522
	 * @throws InvalidPathException
523
	 */
524
	private function scanForInvalidCharacters($fileName, $invalidChars) {
525
		foreach (\str_split($invalidChars) as $char) {
526
			if (\strpos($fileName, $char) !== false) {
527
				throw new InvalidCharacterInPathException();
528
			}
529
		}
530
531
		$sanitizedFileName = \filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
532
		if ($sanitizedFileName !== $fileName) {
533
			throw new InvalidCharacterInPathException();
534
		}
535
	}
536
537
	/**
538
	 * @param array $options
539
	 */
540
	public function setMountOptions(array $options) {
541
		$this->mountOptions = $options;
542
	}
543
544
	/**
545
	 * @param string $name
546
	 * @param mixed $default
547
	 * @return mixed
548
	 */
549
	public function getMountOption($name, $default = null) {
550
		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
551
	}
552
553
	/**
554
	 * @param \OCP\Files\Storage $sourceStorage
555
	 * @param string $sourceInternalPath
556
	 * @param string $targetInternalPath
557
	 * @param bool $preserveMtime
558
	 * @return bool
559
	 * @throws \OCP\Files\StorageNotAvailableException
560
	 */
561
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
562
		if ($sourceStorage === $this) {
563
			return $this->copy($sourceInternalPath, $targetInternalPath);
564
		}
565
566
		if ($sourceStorage->is_dir($sourceInternalPath)) {
567
			$dh = $sourceStorage->opendir($sourceInternalPath);
568
			$result = $this->mkdir($targetInternalPath);
569 View Code Duplication
			if (\is_resource($dh)) {
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...
570
				while ($result and ($file = \readdir($dh)) !== false) {
571
					if (!Filesystem::isIgnoredDir($file) && !Filesystem::isForbiddenFileOrDir($file)) {
572
						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
573
					}
574
				}
575
			}
576
		} else {
577
			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
578
			// TODO: call fopen in a way that we execute again all storage wrappers
579
			// to avoid that we bypass storage wrappers which perform important actions
580
			// for this operation. Same is true for all other operations which
581
			// are not the same as the original one.Once this is fixed we also
582
			// need to adjust the encryption wrapper.
583
			$target = $this->fopen($targetInternalPath, 'w');
584
			list(, $result) = \OC_Helper::streamCopy($source, $target);
0 ignored issues
show
Security Bug introduced by
It seems like $source can also be of type false; however, OC_Helper::streamCopy() does only seem to accept resource, did you maybe forget to handle an error condition?
Loading history...
Security Bug introduced by
It seems like $target can also be of type false; however, OC_Helper::streamCopy() does only seem to accept resource, did you maybe forget to handle an error condition?
Loading history...
585
			if ($result and $preserveMtime) {
586
				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
0 ignored issues
show
Security Bug introduced by
It seems like $sourceStorage->filemtime($sourceInternalPath) targeting OCP\Files\Storage\IStorage::filemtime() can also be of type false; however, OCP\Files\Storage\IStorage::touch() does only seem to accept integer|null, did you maybe forget to handle an error condition?
Loading history...
587
			}
588
			\fclose($source);
589
			\fclose($target);
590
591
			if (!$result) {
592
				// delete partially written target file
593
				$this->unlink($targetInternalPath);
594
				// delete cache entry that was created by fopen
595
				$this->getCache()->remove($targetInternalPath);
596
			}
597
		}
598
		return (bool)$result;
599
	}
600
601
	/**
602
	 * @param \OCP\Files\Storage $sourceStorage
603
	 * @param string $sourceInternalPath
604
	 * @param string $targetInternalPath
605
	 * @return bool
606
	 * @throws \OCP\Files\StorageNotAvailableException
607
	 */
608
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
609
		if ($sourceStorage === $this) {
610
			return $this->rename($sourceInternalPath, $targetInternalPath);
611
		}
612
613
		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
614
			return false;
615
		}
616
617
		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
618
		if ($result) {
619
			if ($sourceStorage->is_dir($sourceInternalPath)) {
620
				$result &= $sourceStorage->rmdir($sourceInternalPath);
621
			} else {
622
				$result &= $sourceStorage->unlink($sourceInternalPath);
623
			}
624
		}
625
		return $result;
626
	}
627
628
	/**
629
	 * @inheritdoc
630
	 */
631
	public function getMetaData($path) {
632
		$permissions = $this->getPermissions($path);
633
		if (!$permissions & Constants::PERMISSION_READ) {
634
			//can't read, nothing we can do
635
			return null;
636
		}
637
638
		$data = [];
639
		$data['mimetype'] = $this->getMimeType($path);
640
		$data['mtime'] = $this->filemtime($path);
641
		if ($data['mtime'] === false) {
642
			$data['mtime'] = \time();
643
		}
644
		if ($data['mimetype'] == 'httpd/unix-directory') {
645
			$data['size'] = -1; //unknown
646
		} else {
647
			$data['size'] = $this->filesize($path);
648
		}
649
		$data['etag'] = $this->getETag($path);
650
		$data['storage_mtime'] = $data['mtime'];
651
		$data['permissions'] = $permissions;
652
653
		return $data;
654
	}
655
656
	/**
657
	 * @param string $path
658
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
659
	 * @param \OCP\Lock\ILockingProvider $provider
660
	 * @throws \OCP\Lock\LockedException
661
	 */
662
	public function acquireLock($path, $type, ILockingProvider $provider) {
663
		$provider->acquireLock('files/' . \md5($this->getId() . '::' . \trim($path, '/')), $type);
664
	}
665
666
	/**
667
	 * @param string $path
668
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
669
	 * @param \OCP\Lock\ILockingProvider $provider
670
	 */
671
	public function releaseLock($path, $type, ILockingProvider $provider) {
672
		$provider->releaseLock('files/' . \md5($this->getId() . '::' . \trim($path, '/')), $type);
673
	}
674
675
	/**
676
	 * @param string $path
677
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
678
	 * @param \OCP\Lock\ILockingProvider $provider
679
	 * @throws \OCP\Lock\LockedException
680
	 */
681
	public function changeLock($path, $type, ILockingProvider $provider) {
682
		$provider->changeLock('files/' . \md5($this->getId() . '::' . \trim($path, '/')), $type);
683
	}
684
685
	/**
686
	 * @return array [ available, last_checked ]
687
	 */
688
	public function getAvailability() {
689
		return $this->getStorageCache()->getAvailability();
690
	}
691
692
	/**
693
	 * @param bool $isAvailable
694
	 */
695
	public function setAvailability($isAvailable) {
696
		$this->getStorageCache()->setAvailability($isAvailable);
697
	}
698
	public function getVersions($internalPath) {
699
		// KISS implementation
700
		if (!\OC_App::isEnabled('files_versions')) {
701
			return [];
702
		}
703
		list($uid, $filename) =  $this->convertInternalPathToGlobalPath($internalPath);
704
705
		return \array_map(function ($version) use ($internalPath) {
706
			$version['mimetype'] = $this->getMimeType($internalPath);
707
			return $version;
708
		}, \array_values(
709
			\OCA\Files_Versions\Storage::getVersions($uid, $filename)));
710
	}
711
712
	/**
713
	 * @param $internalPath
714
	 * @return array
715
	 */
716
	public function convertInternalPathToGlobalPath($internalPath) {
717
		$mounts = \OC::$server->getMountManager()->findByStorageId($this->getId());
718
719
		$selectedMount = \end($mounts);
720
		foreach ($mounts as $mount) {
721
			$o = \explode('/', $mount->getMountPoint());
722
			if ($o[1] === $this->owner) {
723
				$selectedMount = $mount;
724
				break;
725
			}
726
		}
727
728
		$o = \explode('/', $mount->getMountPoint());
0 ignored issues
show
Bug introduced by
The variable $mount seems to be defined by a foreach iteration on line 720. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
729
		$p = $selectedMount->getMountPoint() . $internalPath;
730
		$p = \explode('/', \ltrim($p, '/'));
731
		\array_shift($p);
732
		\array_shift($p);
733
		$p = \implode('/', $p);
734
		return [$o[1], $p];
735
	}
736
737
	public function getVersion($internalPath, $versionId) {
738
		$versions = $this->getVersions($internalPath);
739
		$versions = \array_filter($versions, function ($version) use ($versionId) {
740
			return $version['version'] === $versionId;
741
		});
742
		return \array_shift($versions);
743
	}
744
745
	public function getContentOfVersion($internalPath, $versionId) {
746
		$v = $this->getVersion($internalPath, $versionId);
747
		return \OCA\Files_Versions\Storage::getContentOfVersion($v['owner'], $v['storage_location']);
748
	}
749
750
	public function restoreVersion($internalPath, $versionId) {
751
		// KISS implementation
752
		if (!\OC_App::isEnabled('files_versions')) {
753
			return false;
754
		}
755
		$v = $this->getVersion($internalPath, $versionId);
756
		return \OCA\Files_Versions\Storage::restoreVersion($v['owner'], $v['path'], $v['storage_location'], $versionId);
757
	}
758
759
	public function saveVersion($internalPath) {
760
		// returning false here will trigger the fallback implementation
761
		return false;
762
	}
763
764
	public function lockNodePersistent(string $internalPath, array $lockInfo) : bool {
765
		/** @var LockManager $locksManager */
766
		$locksManager = \OC::$server->query(LockManager::class);
767
		$storageId = $this->getCache()->getNumericStorageId();
768
		$fileId = $this->getCache()->getId($internalPath);
769
		return $locksManager->lock($storageId, $internalPath, $fileId, $lockInfo);
770
	}
771
772
	public function unlockNodePersistent(string $internalPath, array $lockInfo) {
773
		/** @var LockManager $locksManager */
774
		$locksManager = \OC::$server->query(LockManager::class);
775
		$fileId = $this->getCache()->getId($internalPath);
776
		return $locksManager->unlock($fileId, $lockInfo['token']);
777
	}
778
779
	public function getLocks(string $internalPath, bool $returnChildLocks = false) : array {
780
781
		/** @var LockManager $locksManager */
782
		$locksManager = \OC::$server->query(LockManager::class);
783
		$storageId = $this->getCache()->getNumericStorageId();
784
		$locks = $locksManager->getLocks($storageId, $internalPath, $returnChildLocks);
785
786
		return \array_map(function (Lock $lock) {
787
			list($uid, $fileName) = $this->convertInternalPathToGlobalPath($lock->getPath());
788
			$lock->setDavUserId($uid);
0 ignored issues
show
Documentation Bug introduced by
The method setDavUserId does not exist on object<OC\Lock\Persistent\Lock>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
789
			$lock->setAbsoluteDavPath($fileName);
0 ignored issues
show
Documentation Bug introduced by
The method setAbsoluteDavPath does not exist on object<OC\Lock\Persistent\Lock>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
790
			return $lock;
791
		}, $locks);
792
	}
793
}
794