Completed
Push — master ( 0cff70...dba08f )
by Morris
09:32
created

Dropbox::fopen()   C

Complexity

Conditions 21
Paths 73

Size

Total Lines 63
Code Lines 51

Duplication

Lines 23
Ratio 36.51 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 23
loc 63
rs 5.9794
c 1
b 1
f 0
cc 21
eloc 51
nc 73
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Jörn Friedrich Dreyer <[email protected]>
4
 * @author Michael Gapczynski <[email protected]>
5
 * @author Morris Jobke <[email protected]>
6
 * @author Philipp Kapfer <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Sascha Schmidt <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 * @author Vincent Petry <[email protected]>
12
 *
13
 * @copyright Copyright (c) 2016, ownCloud, Inc.
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\Files_External\Lib\Storage;
31
32
use GuzzleHttp\Exception\RequestException;
33
use Icewind\Streams\IteratorDirectory;
34
use Icewind\Streams\RetryWrapper;
35
36
require_once __DIR__ . '/../3rdparty/Dropbox/autoload.php';
37
38
class Dropbox extends \OC\Files\Storage\Common {
39
40
	private $dropbox;
41
	private $root;
42
	private $id;
43
	private $metaData = array();
44
	private $oauth;
45
46
	private static $tempFiles = array();
47
48
	public function __construct($params) {
49
		if (isset($params['configured']) && $params['configured'] == 'true'
50
			&& isset($params['app_key'])
51
			&& isset($params['app_secret'])
52
			&& isset($params['token'])
53
			&& isset($params['token_secret'])
54
		) {
55
			$this->root = isset($params['root']) ? $params['root'] : '';
56
			$this->id = 'dropbox::'.$params['app_key'] . $params['token']. '/' . $this->root;
57
			$this->oauth = new \Dropbox_OAuth_Curl($params['app_key'], $params['app_secret']);
58
			$this->oauth->setToken($params['token'], $params['token_secret']);
59
			// note: Dropbox_API connection is lazy
60
			$this->dropbox = new \Dropbox_API($this->oauth, 'auto');
61
		} else {
62
			throw new \Exception('Creating Dropbox storage failed');
63
		}
64
	}
65
66
	/**
67
	 * @param string $path
68
	 */
69
	private function deleteMetaData($path) {
70
		$path = ltrim($this->root.$path, '/');
71
		if (isset($this->metaData[$path])) {
72
			unset($this->metaData[$path]);
73
			return true;
74
		}
75
		return false;
76
	}
77
78
	private function setMetaData($path, $metaData) {
79
		$this->metaData[ltrim($path, '/')] = $metaData;
80
	}
81
82
	/**
83
	 * Returns the path's metadata
84
	 * @param string $path path for which to return the metadata
85
	 * @param bool $list if true, also return the directory's contents
86
	 * @return mixed directory contents if $list is true, file metadata if $list is
87
	 * false, null if the file doesn't exist or "false" if the operation failed
88
	 */
89
	private function getDropBoxMetaData($path, $list = false) {
90
		$path = ltrim($this->root.$path, '/');
91
		if ( ! $list && isset($this->metaData[$path])) {
92
			return $this->metaData[$path];
93
		} else {
94
			if ($list) {
95
				try {
96
					$response = $this->dropbox->getMetaData($path);
97
				} catch (\Exception $exception) {
98
					\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
99
					return false;
100
				}
101
				$contents = array();
102
				if ($response && isset($response['contents'])) {
103
					// Cache folder's contents
104
					foreach ($response['contents'] as $file) {
105
						if (!isset($file['is_deleted']) || !$file['is_deleted']) {
106
							$this->setMetaData($path.'/'.basename($file['path']), $file);
107
							$contents[] = $file;
108
						}
109
					}
110
					unset($response['contents']);
111
				}
112
				if (!isset($response['is_deleted']) || !$response['is_deleted']) {
113
					$this->setMetaData($path, $response);
114
				}
115
				// Return contents of folder only
116
				return $contents;
117
			} else {
118
				try {
119
					$requestPath = $path;
120
					if ($path === '.') {
121
						$requestPath = '';
122
					}
123
124
					$response = $this->dropbox->getMetaData($requestPath, 'false');
125
					if (!isset($response['is_deleted']) || !$response['is_deleted']) {
126
						$this->setMetaData($path, $response);
127
						return $response;
128
					}
129
					return null;
130
				} catch (\Exception $exception) {
131
					if ($exception instanceof \Dropbox_Exception_NotFound) {
0 ignored issues
show
Bug introduced by
The class Dropbox_Exception_NotFound does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
132
						// don't log, might be a file_exist check
133
						return false;
134
					}
135
					\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
136
					return false;
137
				}
138
			}
139
		}
140
	}
141
142
	public function getId(){
143
		return $this->id;
144
	}
145
146 View Code Duplication
	public function mkdir($path) {
147
		$path = $this->root.$path;
148
		try {
149
			$this->dropbox->createFolder($path);
150
			return true;
151
		} catch (\Exception $exception) {
152
			\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
153
			return false;
154
		}
155
	}
156
157
	public function rmdir($path) {
158
		return $this->unlink($path);
159
	}
160
161
	public function opendir($path) {
162
		$contents = $this->getDropBoxMetaData($path, true);
163
		if ($contents !== false) {
164
			$files = array();
165
			foreach ($contents as $file) {
166
				$files[] = basename($file['path']);
167
			}
168
			return IteratorDirectory::wrap($files);
169
		}
170
		return false;
171
	}
172
173
	public function stat($path) {
174
		$metaData = $this->getDropBoxMetaData($path);
175
		if ($metaData) {
176
			$stat['size'] = $metaData['bytes'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$stat was never initialized. Although not strictly required by PHP, it is generally a good practice to add $stat = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
177
			$stat['atime'] = time();
178
			$stat['mtime'] = (isset($metaData['modified'])) ? strtotime($metaData['modified']) : time();
179
			return $stat;
180
		}
181
		return false;
182
	}
183
184
	public function filetype($path) {
185
		if ($path == '' || $path == '/') {
186
			return 'dir';
187
		} else {
188
			$metaData = $this->getDropBoxMetaData($path);
189
			if ($metaData) {
190
				if ($metaData['is_dir'] == 'true') {
191
					return 'dir';
192
				} else {
193
					return 'file';
194
				}
195
			}
196
		}
197
		return false;
198
	}
199
200
	public function file_exists($path) {
201
		if ($path == '' || $path == '/') {
202
			return true;
203
		}
204
		if ($this->getDropBoxMetaData($path)) {
205
			return true;
206
		}
207
		return false;
208
	}
209
210 View Code Duplication
	public function unlink($path) {
211
		try {
212
			$this->dropbox->delete($this->root.$path);
213
			$this->deleteMetaData($path);
214
			return true;
215
		} catch (\Exception $exception) {
216
			\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
217
			return false;
218
		}
219
	}
220
221
	public function rename($path1, $path2) {
222
		try {
223
			// overwrite if target file exists and is not a directory
224
			$destMetaData = $this->getDropBoxMetaData($path2);
225
			if (isset($destMetaData) && $destMetaData !== false && !$destMetaData['is_dir']) {
226
				$this->unlink($path2);
227
			}
228
			$this->dropbox->move($this->root.$path1, $this->root.$path2);
229
			$this->deleteMetaData($path1);
230
			return true;
231
		} catch (\Exception $exception) {
232
			\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
233
			return false;
234
		}
235
	}
236
237
	public function copy($path1, $path2) {
238
		$path1 = $this->root.$path1;
239
		$path2 = $this->root.$path2;
240
		try {
241
			$this->dropbox->copy($path1, $path2);
242
			return true;
243
		} catch (\Exception $exception) {
244
			\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
245
			return false;
246
		}
247
	}
248
249
	public function fopen($path, $mode) {
250
		$path = $this->root.$path;
251
		switch ($mode) {
252
			case 'r':
253
			case 'rb':
254
				try {
255
					// slashes need to stay
256
					$encodedPath = str_replace('%2F', '/', rawurlencode(trim($path, '/')));
257
					$downloadUrl = 'https://api-content.dropbox.com/1/files/auto/' . $encodedPath;
258
					$headers = $this->oauth->getOAuthHeader($downloadUrl, [], 'GET');
259
260
					$client = \OC::$server->getHTTPClientService()->newClient();
261
					try {
262
						$response = $client->get($downloadUrl, [
263
							'headers' => $headers,
264
							'stream' => true,
265
						]);
266
					} catch (RequestException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\RequestException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
267 View Code Duplication
						if (!is_null($e->getResponse())) {
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...
268
							if ($e->getResponse()->getStatusCode() === 404) {
269
								return false;
270
							} else {
271
								throw $e;
272
							}
273
						} else {
274
							throw $e;
275
						}
276
					}
277
278
					$handle = $response->getBody();
279
					return RetryWrapper::wrap($handle);
280
				} catch (\Exception $exception) {
281
					\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
282
					return false;
283
				}
284
			case 'w':
285
			case 'wb':
286
			case 'a':
287
			case 'ab':
288
			case 'r+':
289
			case 'w+':
290
			case 'wb+':
291
			case 'a+':
292
			case 'x':
293
			case 'x+':
294
			case 'c':
295 View Code Duplication
			case 'c+':
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...
296
				if (strrpos($path, '.') !== false) {
297
					$ext = substr($path, strrpos($path, '.'));
298
				} else {
299
					$ext = '';
300
				}
301
				$tmpFile = \OCP\Files::tmpFile($ext);
302
				\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
303
				if ($this->file_exists($path)) {
304
					$source = $this->fopen($path, 'r');
305
					file_put_contents($tmpFile, $source);
306
				}
307
				self::$tempFiles[$tmpFile] = $path;
308
				return fopen('close://'.$tmpFile, $mode);
309
		}
310
		return false;
311
	}
312
313
	public function writeBack($tmpFile) {
314
		if (isset(self::$tempFiles[$tmpFile])) {
315
			$handle = fopen($tmpFile, 'r');
316
			try {
317
				$this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle);
318
				unlink($tmpFile);
319
				$this->deleteMetaData(self::$tempFiles[$tmpFile]);
320
			} catch (\Exception $exception) {
321
				\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
322
			}
323
		}
324
	}
325
326
	public function free_space($path) {
327
		try {
328
			$info = $this->dropbox->getAccountInfo();
329
			return $info['quota_info']['quota'] - $info['quota_info']['normal'];
0 ignored issues
show
Bug Compatibility introduced by
The expression $info['quota_info']['quo...quota_info']['normal']; of type integer|double adds the type double to the return on line 329 which is incompatible with the return type declared by the interface OCP\Files\Storage::free_space of type integer|false.
Loading history...
330
		} catch (\Exception $exception) {
331
			\OCP\Util::writeLog('files_external', $exception->getMessage(), \OCP\Util::ERROR);
332
			return false;
333
		}
334
	}
335
336 View Code Duplication
	public function touch($path, $mtime = null) {
337
		if ($this->file_exists($path)) {
338
			return false;
339
		} else {
340
			$this->file_put_contents($path, '');
341
		}
342
		return true;
343
	}
344
345
	/**
346
	 * check if curl is installed
347
	 */
348
	public static function checkDependencies() {
349
		return true;
350
	}
351
352
}
353