Passed
Push — master ( 50b380...9b9c1a )
by Julius
16:52 queued 12s
created

Common::isSharable()   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 1
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 Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Greta Doci <[email protected]>
9
 * @author hkjolhede <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Martin Mattel <[email protected]>
14
 * @author Michael Gapczynski <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Robin McCorkell <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Roland Tapken <[email protected]>
20
 * @author Sam Tuke <[email protected]>
21
 * @author scambra <[email protected]>
22
 * @author Stefan Weil <[email protected]>
23
 * @author Thomas Müller <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 * @author Vinicius Cubas Brand <[email protected]>
26
 *
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program. If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
namespace OC\Files\Storage;
44
45
use OC\Files\Cache\Cache;
46
use OC\Files\Cache\Propagator;
47
use OC\Files\Cache\Scanner;
48
use OC\Files\Cache\Updater;
49
use OC\Files\Cache\Watcher;
50
use OC\Files\Filesystem;
51
use OC\Files\Storage\Wrapper\Jail;
52
use OC\Files\Storage\Wrapper\Wrapper;
53
use OCP\Files\EmptyFileNameException;
54
use OCP\Files\FileNameTooLongException;
55
use OCP\Files\GenericFileException;
56
use OCP\Files\InvalidCharacterInPathException;
57
use OCP\Files\InvalidDirectoryException;
58
use OCP\Files\InvalidPathException;
59
use OCP\Files\ReservedWordException;
60
use OCP\Files\Storage\ILockingStorage;
61
use OCP\Files\Storage\IStorage;
62
use OCP\Files\Storage\IWriteStreamStorage;
63
use OCP\ILogger;
64
use OCP\Lock\ILockingProvider;
65
use OCP\Lock\LockedException;
66
67
/**
68
 * Storage backend class for providing common filesystem operation methods
69
 * which are not storage-backend specific.
70
 *
71
 * \OC\Files\Storage\Common is never used directly; it is extended by all other
72
 * storage backends, where its methods may be overridden, and additional
73
 * (backend-specific) methods are defined.
74
 *
75
 * Some \OC\Files\Storage\Common methods call functions which are first defined
76
 * in classes which extend it, e.g. $this->stat() .
77
 */
78
abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
79
	use LocalTempFileTrait;
80
81
	protected $cache;
82
	protected $scanner;
83
	protected $watcher;
84
	protected $propagator;
85
	protected $storageCache;
86
	protected $updater;
87
88
	protected $mountOptions = [];
89
	protected $owner = null;
90
91
	private $shouldLogLocks = null;
92
	private $logger;
93
94
	public function __construct($parameters) {
95
	}
96
97
	/**
98
	 * Remove a file or folder
99
	 *
100
	 * @param string $path
101
	 * @return bool
102
	 */
103
	protected function remove($path) {
104
		if ($this->is_dir($path)) {
105
			return $this->rmdir($path);
106
		} elseif ($this->is_file($path)) {
107
			return $this->unlink($path);
108
		} else {
109
			return false;
110
		}
111
	}
112
113
	public function is_dir($path) {
114
		return $this->filetype($path) === 'dir';
115
	}
116
117
	public function is_file($path) {
118
		return $this->filetype($path) === 'file';
119
	}
120
121
	public function filesize($path) {
122
		if ($this->is_dir($path)) {
123
			return 0; //by definition
124
		} else {
125
			$stat = $this->stat($path);
126
			if (isset($stat['size'])) {
127
				return $stat['size'];
128
			} else {
129
				return 0;
130
			}
131
		}
132
	}
133
134
	public function isReadable($path) {
135
		// at least check whether it exists
136
		// subclasses might want to implement this more thoroughly
137
		return $this->file_exists($path);
138
	}
139
140
	public function isUpdatable($path) {
141
		// at least check whether it exists
142
		// subclasses might want to implement this more thoroughly
143
		// a non-existing file/folder isn't updatable
144
		return $this->file_exists($path);
145
	}
146
147
	public function isCreatable($path) {
148
		if ($this->is_dir($path) && $this->isUpdatable($path)) {
149
			return true;
150
		}
151
		return false;
152
	}
153
154
	public function isDeletable($path) {
155
		if ($path === '' || $path === '/') {
156
			return false;
157
		}
158
		$parent = dirname($path);
159
		return $this->isUpdatable($parent) && $this->isUpdatable($path);
160
	}
161
162
	public function isSharable($path) {
163
		return $this->isReadable($path);
164
	}
165
166
	public function getPermissions($path) {
167
		$permissions = 0;
168
		if ($this->isCreatable($path)) {
169
			$permissions |= \OCP\Constants::PERMISSION_CREATE;
170
		}
171
		if ($this->isReadable($path)) {
172
			$permissions |= \OCP\Constants::PERMISSION_READ;
173
		}
174
		if ($this->isUpdatable($path)) {
175
			$permissions |= \OCP\Constants::PERMISSION_UPDATE;
176
		}
177
		if ($this->isDeletable($path)) {
178
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
179
		}
180
		if ($this->isSharable($path)) {
181
			$permissions |= \OCP\Constants::PERMISSION_SHARE;
182
		}
183
		return $permissions;
184
	}
185
186
	public function filemtime($path) {
187
		$stat = $this->stat($path);
188
		if (isset($stat['mtime']) && $stat['mtime'] > 0) {
189
			return $stat['mtime'];
190
		} else {
191
			return 0;
192
		}
193
	}
194
195
	public function file_get_contents($path) {
196
		$handle = $this->fopen($path, "r");
197
		if (!$handle) {
198
			return false;
199
		}
200
		$data = stream_get_contents($handle);
201
		fclose($handle);
202
		return $data;
203
	}
204
205
	public function file_put_contents($path, $data) {
206
		$handle = $this->fopen($path, "w");
207
		$this->removeCachedFile($path);
208
		$count = fwrite($handle, $data);
209
		fclose($handle);
210
		return $count;
211
	}
212
213
	public function rename($path1, $path2) {
214
		$this->remove($path2);
215
216
		$this->removeCachedFile($path1);
217
		return $this->copy($path1, $path2) and $this->remove($path1);
218
	}
219
220
	public function copy($path1, $path2) {
221
		if ($this->is_dir($path1)) {
222
			$this->remove($path2);
223
			$dir = $this->opendir($path1);
224
			$this->mkdir($path2);
225
			while ($file = readdir($dir)) {
226
				if (!Filesystem::isIgnoredDir($file)) {
227
					if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
228
						return false;
229
					}
230
				}
231
			}
232
			closedir($dir);
233
			return true;
234
		} else {
235
			$source = $this->fopen($path1, 'r');
236
			$target = $this->fopen($path2, 'w');
237
			[, $result] = \OC_Helper::streamCopy($source, $target);
238
			if (!$result) {
239
				\OC::$server->getLogger()->warning("Failed to write data while copying $path1 to $path2");
240
			}
241
			$this->removeCachedFile($path2);
242
			return $result;
243
		}
244
	}
245
246
	public function getMimeType($path) {
247
		if ($this->is_dir($path)) {
248
			return 'httpd/unix-directory';
249
		} elseif ($this->file_exists($path)) {
250
			return \OC::$server->getMimeTypeDetector()->detectPath($path);
251
		} else {
252
			return false;
253
		}
254
	}
255
256
	public function hash($type, $path, $raw = false) {
257
		$fh = $this->fopen($path, 'rb');
258
		$ctx = hash_init($type);
259
		hash_update_stream($ctx, $fh);
260
		fclose($fh);
261
		return hash_final($ctx, $raw);
262
	}
263
264
	public function search($query) {
265
		return $this->searchInDir($query);
266
	}
267
268
	public function getLocalFile($path) {
269
		return $this->getCachedFile($path);
270
	}
271
272
	/**
273
	 * @param string $path
274
	 * @param string $target
275
	 */
276
	private function addLocalFolder($path, $target) {
277
		$dh = $this->opendir($path);
278
		if (is_resource($dh)) {
279
			while (($file = readdir($dh)) !== false) {
280
				if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
281
					if ($this->is_dir($path . '/' . $file)) {
282
						mkdir($target . '/' . $file);
283
						$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
284
					} else {
285
						$tmp = $this->toTmpFile($path . '/' . $file);
286
						rename($tmp, $target . '/' . $file);
287
					}
288
				}
289
			}
290
		}
291
	}
292
293
	/**
294
	 * @param string $query
295
	 * @param string $dir
296
	 * @return array
297
	 */
298
	protected function searchInDir($query, $dir = '') {
299
		$files = [];
300
		$dh = $this->opendir($dir);
301
		if (is_resource($dh)) {
302
			while (($item = readdir($dh)) !== false) {
303
				if (\OC\Files\Filesystem::isIgnoredDir($item)) {
304
					continue;
305
				}
306
				if (strstr(strtolower($item), strtolower($query)) !== false) {
307
					$files[] = $dir . '/' . $item;
308
				}
309
				if ($this->is_dir($dir . '/' . $item)) {
310
					$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
311
				}
312
			}
313
		}
314
		closedir($dh);
315
		return $files;
316
	}
317
318
	/**
319
	 * check if a file or folder has been updated since $time
320
	 *
321
	 * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
322
	 * the mtime should always return false here. As a result storage implementations that always return false expect
323
	 * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
324
	 * ownClouds filesystem.
325
	 *
326
	 * @param string $path
327
	 * @param int $time
328
	 * @return bool
329
	 */
330
	public function hasUpdated($path, $time) {
331
		return $this->filemtime($path) > $time;
332
	}
333
334
	public function getCache($path = '', $storage = null) {
335
		if (!$storage) {
336
			$storage = $this;
337
		}
338
		if (!isset($storage->cache)) {
339
			$storage->cache = new Cache($storage);
340
		}
341
		return $storage->cache;
342
	}
343
344
	public function getScanner($path = '', $storage = null) {
345
		if (!$storage) {
346
			$storage = $this;
347
		}
348
		if (!isset($storage->scanner)) {
349
			$storage->scanner = new Scanner($storage);
350
		}
351
		return $storage->scanner;
352
	}
353
354
	public function getWatcher($path = '', $storage = null) {
355
		if (!$storage) {
356
			$storage = $this;
357
		}
358
		if (!isset($this->watcher)) {
359
			$this->watcher = new Watcher($storage);
360
			$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
361
			$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
362
		}
363
		return $this->watcher;
364
	}
365
366
	/**
367
	 * get a propagator instance for the cache
368
	 *
369
	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
0 ignored issues
show
Bug introduced by
The type OC\Files\Storage\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...
370
	 * @return \OC\Files\Cache\Propagator
371
	 */
372
	public function getPropagator($storage = null) {
373
		if (!$storage) {
374
			$storage = $this;
375
		}
376
		if (!isset($storage->propagator)) {
377
			$config = \OC::$server->getSystemConfig();
378
			$storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
379
		}
380
		return $storage->propagator;
381
	}
382
383
	public function getUpdater($storage = null) {
384
		if (!$storage) {
385
			$storage = $this;
386
		}
387
		if (!isset($storage->updater)) {
388
			$storage->updater = new Updater($storage);
389
		}
390
		return $storage->updater;
391
	}
392
393
	public function getStorageCache($storage = null) {
394
		if (!$storage) {
395
			$storage = $this;
396
		}
397
		if (!isset($this->storageCache)) {
398
			$this->storageCache = new \OC\Files\Cache\Storage($storage);
399
		}
400
		return $this->storageCache;
401
	}
402
403
	/**
404
	 * get the owner of a path
405
	 *
406
	 * @param string $path The path to get the owner
407
	 * @return string|false uid or false
408
	 */
409
	public function getOwner($path) {
410
		if ($this->owner === null) {
411
			$this->owner = \OC_User::getUser();
412
		}
413
414
		return $this->owner;
415
	}
416
417
	/**
418
	 * get the ETag for a file or folder
419
	 *
420
	 * @param string $path
421
	 * @return string
422
	 */
423
	public function getETag($path) {
424
		return uniqid();
425
	}
426
427
	/**
428
	 * clean a path, i.e. remove all redundant '.' and '..'
429
	 * making sure that it can't point to higher than '/'
430
	 *
431
	 * @param string $path The path to clean
432
	 * @return string cleaned path
433
	 */
434
	public function cleanPath($path) {
435
		if (strlen($path) == 0 or $path[0] != '/') {
436
			$path = '/' . $path;
437
		}
438
439
		$output = [];
440
		foreach (explode('/', $path) as $chunk) {
441
			if ($chunk == '..') {
442
				array_pop($output);
443
			} elseif ($chunk == '.') {
444
			} else {
445
				$output[] = $chunk;
446
			}
447
		}
448
		return implode('/', $output);
449
	}
450
451
	/**
452
	 * Test a storage for availability
453
	 *
454
	 * @return bool
455
	 */
456
	public function test() {
457
		try {
458
			if ($this->stat('')) {
459
				return true;
460
			}
461
			\OC::$server->getLogger()->info("External storage not available: stat() failed");
462
			return false;
463
		} catch (\Exception $e) {
464
			\OC::$server->getLogger()->warning("External storage not available: " . $e->getMessage());
465
			\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

465
			\OC::$server->getLogger()->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::WARN]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
466
			return false;
467
		}
468
	}
469
470
	/**
471
	 * get the free space in the storage
472
	 *
473
	 * @param string $path
474
	 * @return int|false
475
	 */
476
	public function free_space($path) {
477
		return \OCP\Files\FileInfo::SPACE_UNKNOWN;
478
	}
479
480
	/**
481
	 * {@inheritdoc}
482
	 */
483
	public function isLocal() {
484
		// the common implementation returns a temporary file by
485
		// default, which is not local
486
		return false;
487
	}
488
489
	/**
490
	 * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
491
	 *
492
	 * @param string $class
493
	 * @return bool
494
	 */
495
	public function instanceOfStorage($class) {
496
		if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
497
			// FIXME Temporary fix to keep existing checks working
498
			$class = '\OCA\Files_Sharing\SharedStorage';
499
		}
500
		return is_a($this, $class);
501
	}
502
503
	/**
504
	 * A custom storage implementation can return an url for direct download of a give file.
505
	 *
506
	 * For now the returned array can hold the parameter url - in future more attributes might follow.
507
	 *
508
	 * @param string $path
509
	 * @return array|false
510
	 */
511
	public function getDirectDownload($path) {
512
		return [];
513
	}
514
515
	/**
516
	 * @inheritdoc
517
	 * @throws InvalidPathException
518
	 */
519
	public function verifyPath($path, $fileName) {
520
521
		// verify empty and dot files
522
		$trimmed = trim($fileName);
523
		if ($trimmed === '') {
524
			throw new EmptyFileNameException();
525
		}
526
527
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
528
			throw new InvalidDirectoryException();
529
		}
530
531
		if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
532
			// verify database - e.g. mysql only 3-byte chars
533
			if (preg_match('%(?:
534
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
535
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
536
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
537
)%xs', $fileName)) {
538
				throw new InvalidCharacterInPathException();
539
			}
540
		}
541
542
		// 255 characters is the limit on common file systems (ext/xfs)
543
		// oc_filecache has a 250 char length limit for the filename
544
		if (isset($fileName[250])) {
545
			throw new FileNameTooLongException();
546
		}
547
548
		// NOTE: $path will remain unverified for now
549
		$this->verifyPosixPath($fileName);
550
	}
551
552
	/**
553
	 * @param string $fileName
554
	 * @throws InvalidPathException
555
	 */
556
	protected function verifyPosixPath($fileName) {
557
		$fileName = trim($fileName);
558
		$this->scanForInvalidCharacters($fileName, "\\/");
559
		$reservedNames = ['*'];
560
		if (in_array($fileName, $reservedNames)) {
561
			throw new ReservedWordException();
562
		}
563
	}
564
565
	/**
566
	 * @param string $fileName
567
	 * @param string $invalidChars
568
	 * @throws InvalidPathException
569
	 */
570
	private function scanForInvalidCharacters($fileName, $invalidChars) {
571
		foreach (str_split($invalidChars) as $char) {
572
			if (strpos($fileName, $char) !== false) {
573
				throw new InvalidCharacterInPathException();
574
			}
575
		}
576
577
		$sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
578
		if ($sanitizedFileName !== $fileName) {
579
			throw new InvalidCharacterInPathException();
580
		}
581
	}
582
583
	/**
584
	 * @param array $options
585
	 */
586
	public function setMountOptions(array $options) {
587
		$this->mountOptions = $options;
588
	}
589
590
	/**
591
	 * @param string $name
592
	 * @param mixed $default
593
	 * @return mixed
594
	 */
595
	public function getMountOption($name, $default = null) {
596
		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
597
	}
598
599
	/**
600
	 * @param IStorage $sourceStorage
601
	 * @param string $sourceInternalPath
602
	 * @param string $targetInternalPath
603
	 * @param bool $preserveMtime
604
	 * @return bool
605
	 */
606
	public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
607
		if ($sourceStorage === $this) {
0 ignored issues
show
introduced by
The condition $sourceStorage === $this is always false.
Loading history...
608
			return $this->copy($sourceInternalPath, $targetInternalPath);
609
		}
610
611
		if ($sourceStorage->is_dir($sourceInternalPath)) {
612
			$dh = $sourceStorage->opendir($sourceInternalPath);
613
			$result = $this->mkdir($targetInternalPath);
614
			if (is_resource($dh)) {
615
				while ($result and ($file = readdir($dh)) !== false) {
616
					if (!Filesystem::isIgnoredDir($file)) {
617
						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
618
					}
619
				}
620
			}
621
		} else {
622
			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
623
			$result = false;
624
			if ($source) {
625
				try {
626
					$this->writeStream($targetInternalPath, $source);
627
					$result = true;
628
				} catch (\Exception $e) {
629
					\OC::$server->getLogger()->logException($e, ['level' => ILogger::WARN, 'message' => 'Failed to copy stream to storage']);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

629
					\OC::$server->getLogger()->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::WARN, 'message' => 'Failed to copy stream to storage']);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
630
				}
631
			}
632
633
			if ($result && $preserveMtime) {
634
				$mtime = $sourceStorage->filemtime($sourceInternalPath);
635
				$this->touch($targetInternalPath, is_int($mtime) ? $mtime : null);
636
			}
637
638
			if (!$result) {
639
				// delete partially written target file
640
				$this->unlink($targetInternalPath);
641
				// delete cache entry that was created by fopen
642
				$this->getCache()->remove($targetInternalPath);
643
			}
644
		}
645
		return (bool)$result;
646
	}
647
648
	/**
649
	 * Check if a storage is the same as the current one, including wrapped storages
650
	 *
651
	 * @param IStorage $storage
652
	 * @return bool
653
	 */
654
	private function isSameStorage(IStorage $storage): bool {
655
		while ($storage->instanceOfStorage(Wrapper::class)) {
656
			/**
657
			 * @var Wrapper $sourceStorage
658
			 */
659
			$storage = $storage->getWrapperStorage();
660
		}
661
662
		return $storage === $this;
663
	}
664
665
	/**
666
	 * @param IStorage $sourceStorage
667
	 * @param string $sourceInternalPath
668
	 * @param string $targetInternalPath
669
	 * @return bool
670
	 */
671
	public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
672
		if ($this->isSameStorage($sourceStorage)) {
673
			// resolve any jailed paths
674
			while ($sourceStorage->instanceOfStorage(Jail::class)) {
675
				/**
676
				 * @var Jail $sourceStorage
677
				 */
678
				$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
679
				$sourceStorage = $sourceStorage->getUnjailedStorage();
680
			}
681
682
			return $this->rename($sourceInternalPath, $targetInternalPath);
683
		}
684
685
		if (!$sourceStorage->isDeletable($sourceInternalPath)) {
686
			return false;
687
		}
688
689
		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
690
		if ($result) {
691
			if ($sourceStorage->is_dir($sourceInternalPath)) {
692
				$result = $result && $sourceStorage->rmdir($sourceInternalPath);
693
			} else {
694
				$result = $result && $sourceStorage->unlink($sourceInternalPath);
695
			}
696
		}
697
		return $result;
698
	}
699
700
	/**
701
	 * @inheritdoc
702
	 */
703
	public function getMetaData($path) {
704
		$permissions = $this->getPermissions($path);
705
		if (!$permissions & \OCP\Constants::PERMISSION_READ) {
706
			//can't read, nothing we can do
707
			return null;
708
		}
709
710
		$data = [];
711
		$data['mimetype'] = $this->getMimeType($path);
712
		$data['mtime'] = $this->filemtime($path);
713
		if ($data['mtime'] === false) {
714
			$data['mtime'] = time();
715
		}
716
		if ($data['mimetype'] == 'httpd/unix-directory') {
717
			$data['size'] = -1; //unknown
718
		} else {
719
			$data['size'] = $this->filesize($path);
720
		}
721
		$data['etag'] = $this->getETag($path);
722
		$data['storage_mtime'] = $data['mtime'];
723
		$data['permissions'] = $permissions;
724
		$data['name'] = basename($path);
725
726
		return $data;
727
	}
728
729
	/**
730
	 * @param string $path
731
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
732
	 * @param \OCP\Lock\ILockingProvider $provider
733
	 * @throws \OCP\Lock\LockedException
734
	 */
735
	public function acquireLock($path, $type, ILockingProvider $provider) {
736
		$logger = $this->getLockLogger();
737
		if ($logger) {
738
			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
739
			$logger->info(
740
				sprintf(
741
					'acquire %s lock on "%s" on storage "%s"',
742
					$typeString,
743
					$path,
744
					$this->getId()
745
				),
746
				[
747
					'app' => 'locking',
748
				]
749
			);
750
		}
751
		try {
752
			$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type, $this->getId() . '::' . $path);
753
		} catch (LockedException $e) {
754
			if ($logger) {
755
				$logger->logException($e, ['level' => ILogger::INFO]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

755
				$logger->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
756
			}
757
			throw $e;
758
		}
759
	}
760
761
	/**
762
	 * @param string $path
763
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
764
	 * @param \OCP\Lock\ILockingProvider $provider
765
	 * @throws \OCP\Lock\LockedException
766
	 */
767
	public function releaseLock($path, $type, ILockingProvider $provider) {
768
		$logger = $this->getLockLogger();
769
		if ($logger) {
770
			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
771
			$logger->info(
772
				sprintf(
773
					'release %s lock on "%s" on storage "%s"',
774
					$typeString,
775
					$path,
776
					$this->getId()
777
				),
778
				[
779
					'app' => 'locking',
780
				]
781
			);
782
		}
783
		try {
784
			$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
785
		} catch (LockedException $e) {
786
			if ($logger) {
787
				$logger->logException($e, ['level' => ILogger::INFO]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

787
				$logger->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
788
			}
789
			throw $e;
790
		}
791
	}
792
793
	/**
794
	 * @param string $path
795
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
796
	 * @param \OCP\Lock\ILockingProvider $provider
797
	 * @throws \OCP\Lock\LockedException
798
	 */
799
	public function changeLock($path, $type, ILockingProvider $provider) {
800
		$logger = $this->getLockLogger();
801
		if ($logger) {
802
			$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
803
			$logger->info(
804
				sprintf(
805
					'change lock on "%s" to %s on storage "%s"',
806
					$path,
807
					$typeString,
808
					$this->getId()
809
				),
810
				[
811
					'app' => 'locking',
812
				]
813
			);
814
		}
815
		try {
816
			$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
817
		} catch (LockedException $e) {
818
			\OC::$server->getLogger()->logException($e, ['level' => ILogger::INFO]);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

818
			\OC::$server->getLogger()->logException($e, ['level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO]);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
819
			throw $e;
820
		}
821
	}
822
823
	private function getLockLogger() {
824
		if (is_null($this->shouldLogLocks)) {
825
			$this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
826
			$this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
827
		}
828
		return $this->logger;
829
	}
830
831
	/**
832
	 * @return array [ available, last_checked ]
833
	 */
834
	public function getAvailability() {
835
		return $this->getStorageCache()->getAvailability();
836
	}
837
838
	/**
839
	 * @param bool $isAvailable
840
	 */
841
	public function setAvailability($isAvailable) {
842
		$this->getStorageCache()->setAvailability($isAvailable);
843
	}
844
845
	/**
846
	 * @return bool
847
	 */
848
	public function needsPartFile() {
849
		return true;
850
	}
851
852
	/**
853
	 * fallback implementation
854
	 *
855
	 * @param string $path
856
	 * @param resource $stream
857
	 * @param int $size
858
	 * @return int
859
	 */
860
	public function writeStream(string $path, $stream, int $size = null): int {
861
		$target = $this->fopen($path, 'w');
862
		if (!$target) {
863
			throw new GenericFileException("Failed to open $path for writing");
864
		}
865
		try {
866
			[$count, $result] = \OC_Helper::streamCopy($stream, $target);
867
			if (!$result) {
0 ignored issues
show
introduced by
The condition $result is always false.
Loading history...
868
				throw new GenericFileException("Failed to copy stream");
869
			}
870
		} finally {
871
			fclose($target);
872
			fclose($stream);
873
		}
874
		return $count;
875
	}
876
877
	public function getDirectoryContent($directory): \Traversable {
878
		$dh = $this->opendir($directory);
879
		if (is_resource($dh)) {
880
			$basePath = rtrim($directory, '/');
881
			while (($file = readdir($dh)) !== false) {
882
				if (!Filesystem::isIgnoredDir($file) && !Filesystem::isFileBlacklisted($file)) {
883
					$childPath = $basePath . '/' . trim($file, '/');
884
					$metadata = $this->getMetaData($childPath);
885
					if ($metadata !== null) {
886
						yield $metadata;
887
					}
888
				}
889
			}
890
		}
891
	}
892
}
893