Completed
Pull Request — master (#32)
by Blizzz
09:50
created

Local::rename()   D

Complexity

Conditions 9
Paths 15

Size

Total Lines 40
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 25
nc 15
nop 2
dl 0
loc 40
rs 4.909
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Brice Maron <[email protected]>
5
 * @author Jakob Sack <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Klaas Freitag <[email protected]>
8
 * @author Martin Mattel <[email protected]>
9
 * @author Michael Gapczynski <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Sjors van der Pluijm <[email protected]>
13
 * @author Stefan Weil <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Tigran Mkrtchyan <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @copyright Copyright (c) 2016, ownCloud, Inc.
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OC\Files\Storage;
36
/**
37
 * for local filestore, we only have to map the paths
38
 */
39
class Local extends \OC\Files\Storage\Common {
40
	protected $datadir;
41
42
	public function __construct($arguments) {
43
		if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
44
			throw new \InvalidArgumentException('No data directory set for local storage');
45
		}
46
		$this->datadir = $arguments['datadir'];
47
		if (substr($this->datadir, -1) !== '/') {
48
			$this->datadir .= '/';
49
		}
50
	}
51
52
	public function __destruct() {
53
	}
54
55
	public function getId() {
56
		return 'local::' . $this->datadir;
57
	}
58
59
	public function mkdir($path) {
60
		return @mkdir($this->getSourcePath($path), 0777, true);
61
	}
62
63
	public function rmdir($path) {
64
		if (!$this->isDeletable($path)) {
65
			return false;
66
		}
67
		try {
68
			$it = new \RecursiveIteratorIterator(
69
				new \RecursiveDirectoryIterator($this->getSourcePath($path)),
70
				\RecursiveIteratorIterator::CHILD_FIRST
71
			);
72
			/**
73
			 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
74
			 * This bug is fixed in PHP 5.5.9 or before
75
			 * See #8376
76
			 */
77
			$it->rewind();
78
			while ($it->valid()) {
79
				/**
80
				 * @var \SplFileInfo $file
81
				 */
82
				$file = $it->current();
83
				if (in_array($file->getBasename(), array('.', '..'))) {
84
					$it->next();
85
					continue;
86
				} elseif ($file->isDir()) {
87
					rmdir($file->getPathname());
88
				} elseif ($file->isFile() || $file->isLink()) {
89
					unlink($file->getPathname());
90
				}
91
				$it->next();
92
			}
93
			return rmdir($this->getSourcePath($path));
94
		} catch (\UnexpectedValueException $e) {
95
			return false;
96
		}
97
	}
98
99
	public function opendir($path) {
100
		return opendir($this->getSourcePath($path));
101
	}
102
103
	public function is_dir($path) {
104
		if (substr($path, -1) == '/') {
105
			$path = substr($path, 0, -1);
106
		}
107
		return is_dir($this->getSourcePath($path));
108
	}
109
110
	public function is_file($path) {
111
		return is_file($this->getSourcePath($path));
112
	}
113
114
	public function stat($path) {
115
		clearstatcache();
116
		$fullPath = $this->getSourcePath($path);
117
		$statResult = stat($fullPath);
118
		if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) {
119
			$filesize = $this->filesize($path);
120
			$statResult['size'] = $filesize;
121
			$statResult[7] = $filesize;
122
		}
123
		return $statResult;
124
	}
125
126
	public function filetype($path) {
127
		$filetype = filetype($this->getSourcePath($path));
128
		if ($filetype == 'link') {
129
			$filetype = filetype(realpath($this->getSourcePath($path)));
130
		}
131
		return $filetype;
132
	}
133
134
	public function filesize($path) {
135
		if ($this->is_dir($path)) {
136
			return 0;
137
		}
138
		$fullPath = $this->getSourcePath($path);
139
		if (PHP_INT_SIZE === 4) {
140
			$helper = new \OC\LargeFileHelper;
141
			return $helper->getFilesize($fullPath);
0 ignored issues
show
Bug Compatibility introduced by
The expression $helper->getFilesize($fullPath); of type integer|double adds the type double to the return on line 141 which is incompatible with the return type declared by the interface OCP\Files\Storage::filesize of type integer|false.
Loading history...
142
		}
143
		return filesize($fullPath);
144
	}
145
146
	public function isReadable($path) {
147
		return is_readable($this->getSourcePath($path));
148
	}
149
150
	public function isUpdatable($path) {
151
		return is_writable($this->getSourcePath($path));
152
	}
153
154
	public function file_exists($path) {
155
		return file_exists($this->getSourcePath($path));
156
	}
157
158
	public function filemtime($path) {
159
		clearstatcache($this->getSourcePath($path));
160
		return filemtime($this->getSourcePath($path));
161
	}
162
163
	public function touch($path, $mtime = null) {
164
		// sets the modification time of the file to the given value.
165
		// If mtime is nil the current time is set.
166
		// note that the access time of the file always changes to the current time.
167
		if ($this->file_exists($path) and !$this->isUpdatable($path)) {
168
			return false;
169
		}
170
		if (!is_null($mtime)) {
171
			$result = touch($this->getSourcePath($path), $mtime);
172
		} else {
173
			$result = touch($this->getSourcePath($path));
174
		}
175
		if ($result) {
176
			clearstatcache(true, $this->getSourcePath($path));
177
		}
178
179
		return $result;
180
	}
181
182
	public function file_get_contents($path) {
183
		// file_get_contents() has a memory leak: https://bugs.php.net/bug.php?id=61961
184
		$fileName = $this->getSourcePath($path);
185
186
		$fileSize = filesize($fileName);
187
		if ($fileSize === 0) {
188
			return '';
189
		}
190
191
		$handle = fopen($fileName,'rb');
192
		$content = fread($handle, $fileSize);
193
		fclose($handle);
194
		return $content;
195
	}
196
197
	public function file_put_contents($path, $data) {
198
		return file_put_contents($this->getSourcePath($path), $data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return file_put_contents...rcePath($path), $data); (integer) is incompatible with the return type declared by the interface OCP\Files\Storage::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...
199
	}
200
201 View Code Duplication
	public function unlink($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...
202
		if ($this->is_dir($path)) {
203
			return $this->rmdir($path);
204
		} else if ($this->is_file($path)) {
205
			return unlink($this->getSourcePath($path));
206
		} else {
207
			return false;
208
		}
209
210
	}
211
212
	public function rename($path1, $path2) {
213
		$srcParent = dirname($path1);
214
		$dstParent = dirname($path2);
215
216
		if (!$this->isUpdatable($srcParent)) {
217
			\OCP\Util::writeLog('core', 'unable to rename, source directory is not writable : ' . $srcParent, \OCP\Util::ERROR);
218
			return false;
219
		}
220
221
		if (!$this->isUpdatable($dstParent)) {
222
			\OCP\Util::writeLog('core', 'unable to rename, destination directory is not writable : ' . $dstParent, \OCP\Util::ERROR);
223
			return false;
224
		}
225
226
		if (!$this->file_exists($path1)) {
227
			\OCP\Util::writeLog('core', 'unable to rename, file does not exists : ' . $path1, \OCP\Util::ERROR);
228
			return false;
229
		}
230
231
		if ($this->is_dir($path2)) {
232
			$this->rmdir($path2);
233
		} else if ($this->is_file($path2)) {
234
			$this->unlink($path2);
235
		}
236
237
		if ($this->is_dir($path1)) {
238
			// we can't move folders across devices, use copy instead
239
			$stat1 = stat(dirname($this->getSourcePath($path1)));
240
			$stat2 = stat(dirname($this->getSourcePath($path2)));
241
			if ($stat1['dev'] !== $stat2['dev']) {
242
				$result = $this->copy($path1, $path2);
243
				if ($result) {
244
					$result &= $this->rmdir($path1);
245
				}
246
				return $result;
247
			}
248
		}
249
250
		return rename($this->getSourcePath($path1), $this->getSourcePath($path2));
251
	}
252
253
	public function copy($path1, $path2) {
254
		if ($this->is_dir($path1)) {
255
			return parent::copy($path1, $path2);
256
		} else {
257
			return copy($this->getSourcePath($path1), $this->getSourcePath($path2));
258
		}
259
	}
260
261
	public function fopen($path, $mode) {
262
		return fopen($this->getSourcePath($path), $mode);
263
	}
264
265
	public function hash($type, $path, $raw = false) {
266
		return hash_file($type, $this->getSourcePath($path), $raw);
267
	}
268
269
	public function free_space($path) {
270
		$sourcePath = $this->getSourcePath($path);
271
		// using !is_dir because $sourcePath might be a part file or
272
		// non-existing file, so we'd still want to use the parent dir
273
		// in such cases
274
		if (!is_dir($sourcePath)) {
275
			// disk_free_space doesn't work on files
276
			$sourcePath = dirname($sourcePath);
277
		}
278
		$space = @disk_free_space($sourcePath);
279
		if ($space === false || is_null($space)) {
280
			return \OCP\Files\FileInfo::SPACE_UNKNOWN;
281
		}
282
		return $space;
283
	}
284
285
	public function search($query) {
286
		return $this->searchInDir($query);
287
	}
288
289
	public function getLocalFile($path) {
290
		return $this->getSourcePath($path);
291
	}
292
293
	public function getLocalFolder($path) {
294
		return $this->getSourcePath($path);
295
	}
296
297
	/**
298
	 * @param string $query
299
	 * @param string $dir
300
	 * @return array
301
	 */
302
	protected function searchInDir($query, $dir = '') {
303
		$files = array();
304
		$physicalDir = $this->getSourcePath($dir);
305
		foreach (scandir($physicalDir) as $item) {
306
			if (\OC\Files\Filesystem::isIgnoredDir($item))
307
				continue;
308
			$physicalItem = $physicalDir . '/' . $item;
309
310 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...
311
				$files[] = $dir . '/' . $item;
312
			}
313
			if (is_dir($physicalItem)) {
314
				$files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
315
			}
316
		}
317
		return $files;
318
	}
319
320
	/**
321
	 * check if a file or folder has been updated since $time
322
	 *
323
	 * @param string $path
324
	 * @param int $time
325
	 * @return bool
326
	 */
327
	public function hasUpdated($path, $time) {
328
		if ($this->file_exists($path)) {
329
			return $this->filemtime($path) > $time;
330
		} else {
331
			return true;
332
		}
333
	}
334
335
	/**
336
	 * Get the source path (on disk) of a given path
337
	 *
338
	 * @param string $path
339
	 * @return string
340
	 */
341
	public function getSourcePath($path) {
342
		$fullPath = $this->datadir . $path;
343
		return $fullPath;
344
	}
345
346
	/**
347
	 * {@inheritdoc}
348
	 */
349
	public function isLocal() {
350
		return true;
351
	}
352
353
	/**
354
	 * get the ETag for a file or folder
355
	 *
356
	 * @param string $path
357
	 * @return string
358
	 */
359
	public function getETag($path) {
360
		if ($this->is_file($path)) {
361
			$stat = $this->stat($path);
362
			return md5(
363
				$stat['mtime'] .
364
				$stat['ino'] .
365
				$stat['dev'] .
366
				$stat['size']
367
			);
368
		} else {
369
			return parent::getETag($path);
370
		}
371
	}
372
373
	/**
374
	 * @param \OCP\Files\Storage $sourceStorage
375
	 * @param string $sourceInternalPath
376
	 * @param string $targetInternalPath
377
	 * @return bool
378
	 */
379 View Code Duplication
	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
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...
380
		if($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')){
381
			/**
382
			 * @var \OC\Files\Storage\Local $sourceStorage
383
			 */
384
			$rootStorage = new Local(['datadir' => '/']);
385
			return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
386
		} else {
387
			return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
388
		}
389
	}
390
391
	/**
392
	 * @param \OCP\Files\Storage $sourceStorage
393
	 * @param string $sourceInternalPath
394
	 * @param string $targetInternalPath
395
	 * @return bool
396
	 */
397 View Code Duplication
	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
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...
398
		if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
399
			/**
400
			 * @var \OC\Files\Storage\Local $sourceStorage
401
			 */
402
			$rootStorage = new Local(['datadir' => '/']);
403
			return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
404
		} else {
405
			return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
406
		}
407
	}
408
}
409