Completed
Pull Request — master (#4212)
by Individual IT
13:52
created
apps/files_external/lib/Lib/Config/IAuthMechanismProvider.php 1 patch
Indentation   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -30,10 +30,10 @@
 block discarded – undo
30 30
  */
31 31
 interface IAuthMechanismProvider {
32 32
 
33
-	/**
34
-	 * @since 9.1.0
35
-	 * @return AuthMechanism[]
36
-	 */
37
-	public function getAuthMechanisms();
33
+    /**
34
+     * @since 9.1.0
35
+     * @return AuthMechanism[]
36
+     */
37
+    public function getAuthMechanisms();
38 38
 
39 39
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/SessionStorageWrapper.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -31,14 +31,14 @@
 block discarded – undo
31 31
  */
32 32
 class SessionStorageWrapper extends PermissionsMask {
33 33
 
34
-	/**
35
-	 * @param array $arguments ['storage' => $storage]
36
-	 */
37
-	public function __construct($arguments) {
38
-		// disable sharing permission
39
-		$arguments['mask'] = Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE;
40
-		parent::__construct($arguments);
41
-	}
34
+    /**
35
+     * @param array $arguments ['storage' => $storage]
36
+     */
37
+    public function __construct($arguments) {
38
+        // disable sharing permission
39
+        $arguments['mask'] = Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE;
40
+        parent::__construct($arguments);
41
+    }
42 42
 
43 43
 }
44 44
 
Please login to merge, or discard this patch.
files_external/lib/Lib/InsufficientDataForMeaningfulAnswerException.php 1 patch
Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -29,15 +29,15 @@
 block discarded – undo
29 29
  * Authentication mechanism or backend has insufficient data
30 30
  */
31 31
 class InsufficientDataForMeaningfulAnswerException extends StorageNotAvailableException {
32
-	/**
33
-	 * StorageNotAvailableException constructor.
34
-	 *
35
-	 * @param string $message
36
-	 * @param int $code
37
-	 * @param \Exception $previous
38
-	 * @since 6.0.0
39
-	 */
40
-	public function __construct($message = '', $code = self::STATUS_INDETERMINATE, \Exception $previous = null) {
41
-		parent::__construct($message, $code, $previous);
42
-	}
32
+    /**
33
+     * StorageNotAvailableException constructor.
34
+     *
35
+     * @param string $message
36
+     * @param int $code
37
+     * @param \Exception $previous
38
+     * @since 6.0.0
39
+     */
40
+    public function __construct($message = '', $code = self::STATUS_INDETERMINATE, \Exception $previous = null) {
41
+        parent::__construct($message, $code, $previous);
42
+    }
43 43
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/Google.php 1 patch
Indentation   +675 added lines, -675 removed lines patch added patch discarded remove patch
@@ -41,684 +41,684 @@
 block discarded – undo
41 41
 use Icewind\Streams\RetryWrapper;
42 42
 
43 43
 set_include_path(get_include_path().PATH_SEPARATOR.
44
-	\OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src');
44
+    \OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src');
45 45
 require_once 'Google/autoload.php';
46 46
 
47 47
 class Google extends \OC\Files\Storage\Common {
48 48
 
49
-	private $client;
50
-	private $id;
51
-	private $service;
52
-	private $driveFiles;
53
-
54
-	// Google Doc mimetypes
55
-	const FOLDER = 'application/vnd.google-apps.folder';
56
-	const DOCUMENT = 'application/vnd.google-apps.document';
57
-	const SPREADSHEET = 'application/vnd.google-apps.spreadsheet';
58
-	const DRAWING = 'application/vnd.google-apps.drawing';
59
-	const PRESENTATION = 'application/vnd.google-apps.presentation';
60
-	const MAP = 'application/vnd.google-apps.map';
61
-
62
-	public function __construct($params) {
63
-		if (isset($params['configured']) && $params['configured'] === 'true'
64
-			&& isset($params['client_id']) && isset($params['client_secret'])
65
-			&& isset($params['token'])
66
-		) {
67
-			$this->client = new \Google_Client();
68
-			$this->client->setClientId($params['client_id']);
69
-			$this->client->setClientSecret($params['client_secret']);
70
-			$this->client->setScopes(array('https://www.googleapis.com/auth/drive'));
71
-			$this->client->setAccessToken($params['token']);
72
-			// if curl isn't available we're likely to run into
73
-			// https://github.com/google/google-api-php-client/issues/59
74
-			// - disable gzip to avoid it.
75
-			if (!function_exists('curl_version') || !function_exists('curl_exec')) {
76
-				$this->client->setClassConfig("Google_Http_Request", "disable_gzip", true);
77
-			}
78
-			// note: API connection is lazy
79
-			$this->service = new \Google_Service_Drive($this->client);
80
-			$token = json_decode($params['token'], true);
81
-			$this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created'];
82
-		} else {
83
-			throw new \Exception('Creating Google storage failed');
84
-		}
85
-	}
86
-
87
-	public function getId() {
88
-		return $this->id;
89
-	}
90
-
91
-	/**
92
-	 * Get the Google_Service_Drive_DriveFile object for the specified path.
93
-	 * Returns false on failure.
94
-	 * @param string $path
95
-	 * @return \Google_Service_Drive_DriveFile|false
96
-	 */
97
-	private function getDriveFile($path) {
98
-		// Remove leading and trailing slashes
99
-		$path = trim($path, '/');
100
-		if ($path === '.') {
101
-			$path = '';
102
-		}
103
-		if (isset($this->driveFiles[$path])) {
104
-			return $this->driveFiles[$path];
105
-		} else if ($path === '') {
106
-			$root = $this->service->files->get('root');
107
-			$this->driveFiles[$path] = $root;
108
-			return $root;
109
-		} else {
110
-			// Google Drive SDK does not have methods for retrieving files by path
111
-			// Instead we must find the id of the parent folder of the file
112
-			$parentId = $this->getDriveFile('')->getId();
113
-			$folderNames = explode('/', $path);
114
-			$path = '';
115
-			// Loop through each folder of this path to get to the file
116
-			foreach ($folderNames as $name) {
117
-				// Reconstruct path from beginning
118
-				if ($path === '') {
119
-					$path .= $name;
120
-				} else {
121
-					$path .= '/'.$name;
122
-				}
123
-				if (isset($this->driveFiles[$path])) {
124
-					$parentId = $this->driveFiles[$path]->getId();
125
-				} else {
126
-					$q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false";
127
-					$result = $this->service->files->listFiles(array('q' => $q))->getItems();
128
-					if (!empty($result)) {
129
-						// Google Drive allows files with the same name, ownCloud doesn't
130
-						if (count($result) > 1) {
131
-							$this->onDuplicateFileDetected($path);
132
-							return false;
133
-						} else {
134
-							$file = current($result);
135
-							$this->driveFiles[$path] = $file;
136
-							$parentId = $file->getId();
137
-						}
138
-					} else {
139
-						// Google Docs have no extension in their title, so try without extension
140
-						$pos = strrpos($path, '.');
141
-						if ($pos !== false) {
142
-							$pathWithoutExt = substr($path, 0, $pos);
143
-							$file = $this->getDriveFile($pathWithoutExt);
144
-							if ($file && $this->isGoogleDocFile($file)) {
145
-								// Switch cached Google_Service_Drive_DriveFile to the correct index
146
-								unset($this->driveFiles[$pathWithoutExt]);
147
-								$this->driveFiles[$path] = $file;
148
-								$parentId = $file->getId();
149
-							} else {
150
-								return false;
151
-							}
152
-						} else {
153
-							return false;
154
-						}
155
-					}
156
-				}
157
-			}
158
-			return $this->driveFiles[$path];
159
-		}
160
-	}
161
-
162
-	/**
163
-	 * Set the Google_Service_Drive_DriveFile object in the cache
164
-	 * @param string $path
165
-	 * @param \Google_Service_Drive_DriveFile|false $file
166
-	 */
167
-	private function setDriveFile($path, $file) {
168
-		$path = trim($path, '/');
169
-		$this->driveFiles[$path] = $file;
170
-		if ($file === false) {
171
-			// Remove all children
172
-			$len = strlen($path);
173
-			foreach ($this->driveFiles as $key => $file) {
174
-				if (substr($key, 0, $len) === $path) {
175
-					unset($this->driveFiles[$key]);
176
-				}
177
-			}
178
-		}
179
-	}
180
-
181
-	/**
182
-	 * Write a log message to inform about duplicate file names
183
-	 * @param string $path
184
-	 */
185
-	private function onDuplicateFileDetected($path) {
186
-		$about = $this->service->about->get();
187
-		$user = $about->getName();
188
-		\OCP\Util::writeLog('files_external',
189
-			'Ignoring duplicate file name: '.$path.' on Google Drive for Google user: '.$user,
190
-			\OCP\Util::INFO
191
-		);
192
-	}
193
-
194
-	/**
195
-	 * Generate file extension for a Google Doc, choosing Open Document formats for download
196
-	 * @param string $mimetype
197
-	 * @return string
198
-	 */
199
-	private function getGoogleDocExtension($mimetype) {
200
-		if ($mimetype === self::DOCUMENT) {
201
-			return 'odt';
202
-		} else if ($mimetype === self::SPREADSHEET) {
203
-			return 'ods';
204
-		} else if ($mimetype === self::DRAWING) {
205
-			return 'jpg';
206
-		} else if ($mimetype === self::PRESENTATION) {
207
-			// Download as .odp is not available
208
-			return 'pdf';
209
-		} else {
210
-			return '';
211
-		}
212
-	}
213
-
214
-	/**
215
-	 * Returns whether the given drive file is a Google Doc file
216
-	 *
217
-	 * @param \Google_Service_Drive_DriveFile
218
-	 *
219
-	 * @return true if the file is a Google Doc file, false otherwise
220
-	 */
221
-	private function isGoogleDocFile($file) {
222
-		return $this->getGoogleDocExtension($file->getMimeType()) !== '';
223
-	}
224
-
225
-	public function mkdir($path) {
226
-		if (!$this->is_dir($path)) {
227
-			$parentFolder = $this->getDriveFile(dirname($path));
228
-			if ($parentFolder) {
229
-				$folder = new \Google_Service_Drive_DriveFile();
230
-				$folder->setTitle(basename($path));
231
-				$folder->setMimeType(self::FOLDER);
232
-				$parent = new \Google_Service_Drive_ParentReference();
233
-				$parent->setId($parentFolder->getId());
234
-				$folder->setParents(array($parent));
235
-				$result = $this->service->files->insert($folder);
236
-				if ($result) {
237
-					$this->setDriveFile($path, $result);
238
-				}
239
-				return (bool)$result;
240
-			}
241
-		}
242
-		return false;
243
-	}
244
-
245
-	public function rmdir($path) {
246
-		if (!$this->isDeletable($path)) {
247
-			return false;
248
-		}
249
-		if (trim($path, '/') === '') {
250
-			$dir = $this->opendir($path);
251
-			if(is_resource($dir)) {
252
-				while (($file = readdir($dir)) !== false) {
253
-					if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
254
-						if (!$this->unlink($path.'/'.$file)) {
255
-							return false;
256
-						}
257
-					}
258
-				}
259
-				closedir($dir);
260
-			}
261
-			$this->driveFiles = array();
262
-			return true;
263
-		} else {
264
-			return $this->unlink($path);
265
-		}
266
-	}
267
-
268
-	public function opendir($path) {
269
-		$folder = $this->getDriveFile($path);
270
-		if ($folder) {
271
-			$files = array();
272
-			$duplicates = array();
273
-			$pageToken = true;
274
-			while ($pageToken) {
275
-				$params = array();
276
-				if ($pageToken !== true) {
277
-					$params['pageToken'] = $pageToken;
278
-				}
279
-				$params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false";
280
-				$children = $this->service->files->listFiles($params);
281
-				foreach ($children->getItems() as $child) {
282
-					$name = $child->getTitle();
283
-					// Check if this is a Google Doc i.e. no extension in name
284
-					$extension = $child->getFileExtension();
285
-					if (empty($extension)) {
286
-						if ($child->getMimeType() === self::MAP) {
287
-							continue; // No method known to transfer map files, ignore it
288
-						} else if ($child->getMimeType() !== self::FOLDER) {
289
-							$name .= '.'.$this->getGoogleDocExtension($child->getMimeType());
290
-						}
291
-					}
292
-					if ($path === '') {
293
-						$filepath = $name;
294
-					} else {
295
-						$filepath = $path.'/'.$name;
296
-					}
297
-					// Google Drive allows files with the same name, ownCloud doesn't
298
-					// Prevent opendir() from returning any duplicate files
299
-					$key = array_search($name, $files);
300
-					if ($key !== false || isset($duplicates[$filepath])) {
301
-						if (!isset($duplicates[$filepath])) {
302
-							$duplicates[$filepath] = true;
303
-							$this->setDriveFile($filepath, false);
304
-							unset($files[$key]);
305
-							$this->onDuplicateFileDetected($filepath);
306
-						}
307
-					} else {
308
-						// Cache the Google_Service_Drive_DriveFile for future use
309
-						$this->setDriveFile($filepath, $child);
310
-						$files[] = $name;
311
-					}
312
-				}
313
-				$pageToken = $children->getNextPageToken();
314
-			}
315
-			return IteratorDirectory::wrap($files);
316
-		} else {
317
-			return false;
318
-		}
319
-	}
320
-
321
-	public function stat($path) {
322
-		$file = $this->getDriveFile($path);
323
-		if ($file) {
324
-			$stat = array();
325
-			if ($this->filetype($path) === 'dir') {
326
-				$stat['size'] = 0;
327
-			} else {
328
-				// Check if this is a Google Doc
329
-				if ($this->isGoogleDocFile($file)) {
330
-					// Return unknown file size
331
-					$stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN;
332
-				} else {
333
-					$stat['size'] = $file->getFileSize();
334
-				}
335
-			}
336
-			$stat['atime'] = strtotime($file->getLastViewedByMeDate());
337
-			$stat['mtime'] = strtotime($file->getModifiedDate());
338
-			$stat['ctime'] = strtotime($file->getCreatedDate());
339
-			return $stat;
340
-		} else {
341
-			return false;
342
-		}
343
-	}
344
-
345
-	public function filetype($path) {
346
-		if ($path === '') {
347
-			return 'dir';
348
-		} else {
349
-			$file = $this->getDriveFile($path);
350
-			if ($file) {
351
-				if ($file->getMimeType() === self::FOLDER) {
352
-					return 'dir';
353
-				} else {
354
-					return 'file';
355
-				}
356
-			} else {
357
-				return false;
358
-			}
359
-		}
360
-	}
361
-
362
-	public function isUpdatable($path) {
363
-		$file = $this->getDriveFile($path);
364
-		if ($file) {
365
-			return $file->getEditable();
366
-		} else {
367
-			return false;
368
-		}
369
-	}
370
-
371
-	public function file_exists($path) {
372
-		return (bool)$this->getDriveFile($path);
373
-	}
374
-
375
-	public function unlink($path) {
376
-		$file = $this->getDriveFile($path);
377
-		if ($file) {
378
-			$result = $this->service->files->trash($file->getId());
379
-			if ($result) {
380
-				$this->setDriveFile($path, false);
381
-			}
382
-			return (bool)$result;
383
-		} else {
384
-			return false;
385
-		}
386
-	}
387
-
388
-	public function rename($path1, $path2) {
389
-		$file = $this->getDriveFile($path1);
390
-		if ($file) {
391
-			$newFile = $this->getDriveFile($path2);
392
-			if (dirname($path1) === dirname($path2)) {
393
-				if ($newFile) {
394
-					// rename to the name of the target file, could be an office file without extension
395
-					$file->setTitle($newFile->getTitle());
396
-				} else {
397
-					$file->setTitle(basename(($path2)));
398
-				}
399
-			} else {
400
-				// Change file parent
401
-				$parentFolder2 = $this->getDriveFile(dirname($path2));
402
-				if ($parentFolder2) {
403
-					$parent = new \Google_Service_Drive_ParentReference();
404
-					$parent->setId($parentFolder2->getId());
405
-					$file->setParents(array($parent));
406
-				} else {
407
-					return false;
408
-				}
409
-			}
410
-			// We need to get the object for the existing file with the same
411
-			// name (if there is one) before we do the patch. If oldfile
412
-			// exists and is a directory we have to delete it before we
413
-			// do the rename too.
414
-			$oldfile = $this->getDriveFile($path2);
415
-			if ($oldfile && $this->is_dir($path2)) {
416
-				$this->rmdir($path2);
417
-				$oldfile = false;
418
-			}
419
-			$result = $this->service->files->patch($file->getId(), $file);
420
-			if ($result) {
421
-				$this->setDriveFile($path1, false);
422
-				$this->setDriveFile($path2, $result);
423
-				if ($oldfile && $newFile) {
424
-					// only delete if they have a different id (same id can happen for part files)
425
-					if ($newFile->getId() !== $oldfile->getId()) {
426
-						$this->service->files->delete($oldfile->getId());
427
-					}
428
-				}
429
-			}
430
-			return (bool)$result;
431
-		} else {
432
-			return false;
433
-		}
434
-	}
435
-
436
-	public function fopen($path, $mode) {
437
-		$pos = strrpos($path, '.');
438
-		if ($pos !== false) {
439
-			$ext = substr($path, $pos);
440
-		} else {
441
-			$ext = '';
442
-		}
443
-		switch ($mode) {
444
-			case 'r':
445
-			case 'rb':
446
-				$file = $this->getDriveFile($path);
447
-				if ($file) {
448
-					$exportLinks = $file->getExportLinks();
449
-					$mimetype = $this->getMimeType($path);
450
-					$downloadUrl = null;
451
-					if ($exportLinks && isset($exportLinks[$mimetype])) {
452
-						$downloadUrl = $exportLinks[$mimetype];
453
-					} else {
454
-						$downloadUrl = $file->getDownloadUrl();
455
-					}
456
-					if (isset($downloadUrl)) {
457
-						$request = new \Google_Http_Request($downloadUrl, 'GET', null, null);
458
-						$httpRequest = $this->client->getAuth()->sign($request);
459
-						// the library's service doesn't support streaming, so we use Guzzle instead
460
-						$client = \OC::$server->getHTTPClientService()->newClient();
461
-						try {
462
-							$response = $client->get($downloadUrl, [
463
-								'headers' => $httpRequest->getRequestHeaders(),
464
-								'stream' => true,
465
-								'verify' => realpath(__DIR__ . '/../../../3rdparty/google-api-php-client/src/Google/IO/cacerts.pem'),
466
-							]);
467
-						} catch (RequestException $e) {
468
-							if(!is_null($e->getResponse())) {
469
-								if ($e->getResponse()->getStatusCode() === 404) {
470
-									return false;
471
-								} else {
472
-									throw $e;
473
-								}
474
-							} else {
475
-								throw $e;
476
-							}
477
-						}
478
-
479
-						$handle = $response->getBody();
480
-						return RetryWrapper::wrap($handle);
481
-					}
482
-				}
483
-				return false;
484
-			case 'w':
485
-			case 'wb':
486
-			case 'a':
487
-			case 'ab':
488
-			case 'r+':
489
-			case 'w+':
490
-			case 'wb+':
491
-			case 'a+':
492
-			case 'x':
493
-			case 'x+':
494
-			case 'c':
495
-			case 'c+':
496
-				$tmpFile = \OCP\Files::tmpFile($ext);
497
-				if ($this->file_exists($path)) {
498
-					$source = $this->fopen($path, 'rb');
499
-					file_put_contents($tmpFile, $source);
500
-				}
501
-				$handle = fopen($tmpFile, $mode);
502
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
503
-					$this->writeBack($tmpFile, $path);
504
-				});
505
-		}
506
-	}
507
-
508
-	public function writeBack($tmpFile, $path) {
509
-		$parentFolder = $this->getDriveFile(dirname($path));
510
-		if ($parentFolder) {
511
-			$mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
512
-			$params = array(
513
-				'mimeType' => $mimetype,
514
-				'uploadType' => 'media'
515
-			);
516
-			$result = false;
517
-
518
-			$chunkSizeBytes = 10 * 1024 * 1024;
519
-
520
-			$useChunking = false;
521
-			$size = filesize($tmpFile);
522
-			if ($size > $chunkSizeBytes) {
523
-				$useChunking = true;
524
-			} else {
525
-				$params['data'] = file_get_contents($tmpFile);
526
-			}
527
-
528
-			if ($this->file_exists($path)) {
529
-				$file = $this->getDriveFile($path);
530
-				$this->client->setDefer($useChunking);
531
-				$request = $this->service->files->update($file->getId(), $file, $params);
532
-			} else {
533
-				$file = new \Google_Service_Drive_DriveFile();
534
-				$file->setTitle(basename($path));
535
-				$file->setMimeType($mimetype);
536
-				$parent = new \Google_Service_Drive_ParentReference();
537
-				$parent->setId($parentFolder->getId());
538
-				$file->setParents(array($parent));
539
-				$this->client->setDefer($useChunking);
540
-				$request = $this->service->files->insert($file, $params);
541
-			}
542
-
543
-			if ($useChunking) {
544
-				// Create a media file upload to represent our upload process.
545
-				$media = new \Google_Http_MediaFileUpload(
546
-					$this->client,
547
-					$request,
548
-					'text/plain',
549
-					null,
550
-					true,
551
-					$chunkSizeBytes
552
-				);
553
-				$media->setFileSize($size);
554
-
555
-				// Upload the various chunks. $status will be false until the process is
556
-				// complete.
557
-				$status = false;
558
-				$handle = fopen($tmpFile, 'rb');
559
-				while (!$status && !feof($handle)) {
560
-					$chunk = fread($handle, $chunkSizeBytes);
561
-					$status = $media->nextChunk($chunk);
562
-				}
563
-
564
-				// The final value of $status will be the data from the API for the object
565
-				// that has been uploaded.
566
-				$result = false;
567
-				if ($status !== false) {
568
-					$result = $status;
569
-				}
570
-
571
-				fclose($handle);
572
-			} else {
573
-				$result = $request;
574
-			}
575
-
576
-			// Reset to the client to execute requests immediately in the future.
577
-			$this->client->setDefer(false);
578
-
579
-			if ($result) {
580
-				$this->setDriveFile($path, $result);
581
-			}
582
-		}
583
-	}
584
-
585
-	public function getMimeType($path) {
586
-		$file = $this->getDriveFile($path);
587
-		if ($file) {
588
-			$mimetype = $file->getMimeType();
589
-			// Convert Google Doc mimetypes, choosing Open Document formats for download
590
-			if ($mimetype === self::FOLDER) {
591
-				return 'httpd/unix-directory';
592
-			} else if ($mimetype === self::DOCUMENT) {
593
-				return 'application/vnd.oasis.opendocument.text';
594
-			} else if ($mimetype === self::SPREADSHEET) {
595
-				return 'application/x-vnd.oasis.opendocument.spreadsheet';
596
-			} else if ($mimetype === self::DRAWING) {
597
-				return 'image/jpeg';
598
-			} else if ($mimetype === self::PRESENTATION) {
599
-				// Download as .odp is not available
600
-				return 'application/pdf';
601
-			} else {
602
-				// use extension-based detection, could be an encrypted file
603
-				return parent::getMimeType($path);
604
-			}
605
-		} else {
606
-			return false;
607
-		}
608
-	}
609
-
610
-	public function free_space($path) {
611
-		$about = $this->service->about->get();
612
-		return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
613
-	}
614
-
615
-	public function touch($path, $mtime = null) {
616
-		$file = $this->getDriveFile($path);
617
-		$result = false;
618
-		if ($file) {
619
-			if (isset($mtime)) {
620
-				// This is just RFC3339, but frustratingly, GDrive's API *requires*
621
-				// the fractions portion be present, while no handy PHP constant
622
-				// for RFC3339 or ISO8601 includes it. So we do it ourselves.
623
-				$file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime));
624
-				$result = $this->service->files->patch($file->getId(), $file, array(
625
-					'setModifiedDate' => true,
626
-				));
627
-			} else {
628
-				$result = $this->service->files->touch($file->getId());
629
-			}
630
-		} else {
631
-			$parentFolder = $this->getDriveFile(dirname($path));
632
-			if ($parentFolder) {
633
-				$file = new \Google_Service_Drive_DriveFile();
634
-				$file->setTitle(basename($path));
635
-				$parent = new \Google_Service_Drive_ParentReference();
636
-				$parent->setId($parentFolder->getId());
637
-				$file->setParents(array($parent));
638
-				$result = $this->service->files->insert($file);
639
-			}
640
-		}
641
-		if ($result) {
642
-			$this->setDriveFile($path, $result);
643
-		}
644
-		return (bool)$result;
645
-	}
646
-
647
-	public function test() {
648
-		if ($this->free_space('')) {
649
-			return true;
650
-		}
651
-		return false;
652
-	}
653
-
654
-	public function hasUpdated($path, $time) {
655
-		$appConfig = \OC::$server->getAppConfig();
656
-		if ($this->is_file($path)) {
657
-			return parent::hasUpdated($path, $time);
658
-		} else {
659
-			// Google Drive doesn't change modified times of folders when files inside are updated
660
-			// Instead we use the Changes API to see if folders have been updated, and it's a pain
661
-			$folder = $this->getDriveFile($path);
662
-			if ($folder) {
663
-				$result = false;
664
-				$folderId = $folder->getId();
665
-				$startChangeId = $appConfig->getValue('files_external', $this->getId().'cId');
666
-				$params = array(
667
-					'includeDeleted' => true,
668
-					'includeSubscribed' => true,
669
-				);
670
-				if (isset($startChangeId)) {
671
-					$startChangeId = (int)$startChangeId;
672
-					$largestChangeId = $startChangeId;
673
-					$params['startChangeId'] = $startChangeId + 1;
674
-				} else {
675
-					$largestChangeId = 0;
676
-				}
677
-				$pageToken = true;
678
-				while ($pageToken) {
679
-					if ($pageToken !== true) {
680
-						$params['pageToken'] = $pageToken;
681
-					}
682
-					$changes = $this->service->changes->listChanges($params);
683
-					if ($largestChangeId === 0 || $largestChangeId === $startChangeId) {
684
-						$largestChangeId = $changes->getLargestChangeId();
685
-					}
686
-					if (isset($startChangeId)) {
687
-						// Check if a file in this folder has been updated
688
-						// There is no way to filter by folder at the API level...
689
-						foreach ($changes->getItems() as $change) {
690
-							$file = $change->getFile();
691
-							if ($file) {
692
-								foreach ($file->getParents() as $parent) {
693
-									if ($parent->getId() === $folderId) {
694
-										$result = true;
695
-									// Check if there are changes in different folders
696
-									} else if ($change->getId() <= $largestChangeId) {
697
-										// Decrement id so this change is fetched when called again
698
-										$largestChangeId = $change->getId();
699
-										$largestChangeId--;
700
-									}
701
-								}
702
-							}
703
-						}
704
-						$pageToken = $changes->getNextPageToken();
705
-					} else {
706
-						// Assuming the initial scan just occurred and changes are negligible
707
-						break;
708
-					}
709
-				}
710
-				$appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId);
711
-				return $result;
712
-			}
713
-		}
714
-		return false;
715
-	}
716
-
717
-	/**
718
-	 * check if curl is installed
719
-	 */
720
-	public static function checkDependencies() {
721
-		return true;
722
-	}
49
+    private $client;
50
+    private $id;
51
+    private $service;
52
+    private $driveFiles;
53
+
54
+    // Google Doc mimetypes
55
+    const FOLDER = 'application/vnd.google-apps.folder';
56
+    const DOCUMENT = 'application/vnd.google-apps.document';
57
+    const SPREADSHEET = 'application/vnd.google-apps.spreadsheet';
58
+    const DRAWING = 'application/vnd.google-apps.drawing';
59
+    const PRESENTATION = 'application/vnd.google-apps.presentation';
60
+    const MAP = 'application/vnd.google-apps.map';
61
+
62
+    public function __construct($params) {
63
+        if (isset($params['configured']) && $params['configured'] === 'true'
64
+            && isset($params['client_id']) && isset($params['client_secret'])
65
+            && isset($params['token'])
66
+        ) {
67
+            $this->client = new \Google_Client();
68
+            $this->client->setClientId($params['client_id']);
69
+            $this->client->setClientSecret($params['client_secret']);
70
+            $this->client->setScopes(array('https://www.googleapis.com/auth/drive'));
71
+            $this->client->setAccessToken($params['token']);
72
+            // if curl isn't available we're likely to run into
73
+            // https://github.com/google/google-api-php-client/issues/59
74
+            // - disable gzip to avoid it.
75
+            if (!function_exists('curl_version') || !function_exists('curl_exec')) {
76
+                $this->client->setClassConfig("Google_Http_Request", "disable_gzip", true);
77
+            }
78
+            // note: API connection is lazy
79
+            $this->service = new \Google_Service_Drive($this->client);
80
+            $token = json_decode($params['token'], true);
81
+            $this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created'];
82
+        } else {
83
+            throw new \Exception('Creating Google storage failed');
84
+        }
85
+    }
86
+
87
+    public function getId() {
88
+        return $this->id;
89
+    }
90
+
91
+    /**
92
+     * Get the Google_Service_Drive_DriveFile object for the specified path.
93
+     * Returns false on failure.
94
+     * @param string $path
95
+     * @return \Google_Service_Drive_DriveFile|false
96
+     */
97
+    private function getDriveFile($path) {
98
+        // Remove leading and trailing slashes
99
+        $path = trim($path, '/');
100
+        if ($path === '.') {
101
+            $path = '';
102
+        }
103
+        if (isset($this->driveFiles[$path])) {
104
+            return $this->driveFiles[$path];
105
+        } else if ($path === '') {
106
+            $root = $this->service->files->get('root');
107
+            $this->driveFiles[$path] = $root;
108
+            return $root;
109
+        } else {
110
+            // Google Drive SDK does not have methods for retrieving files by path
111
+            // Instead we must find the id of the parent folder of the file
112
+            $parentId = $this->getDriveFile('')->getId();
113
+            $folderNames = explode('/', $path);
114
+            $path = '';
115
+            // Loop through each folder of this path to get to the file
116
+            foreach ($folderNames as $name) {
117
+                // Reconstruct path from beginning
118
+                if ($path === '') {
119
+                    $path .= $name;
120
+                } else {
121
+                    $path .= '/'.$name;
122
+                }
123
+                if (isset($this->driveFiles[$path])) {
124
+                    $parentId = $this->driveFiles[$path]->getId();
125
+                } else {
126
+                    $q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false";
127
+                    $result = $this->service->files->listFiles(array('q' => $q))->getItems();
128
+                    if (!empty($result)) {
129
+                        // Google Drive allows files with the same name, ownCloud doesn't
130
+                        if (count($result) > 1) {
131
+                            $this->onDuplicateFileDetected($path);
132
+                            return false;
133
+                        } else {
134
+                            $file = current($result);
135
+                            $this->driveFiles[$path] = $file;
136
+                            $parentId = $file->getId();
137
+                        }
138
+                    } else {
139
+                        // Google Docs have no extension in their title, so try without extension
140
+                        $pos = strrpos($path, '.');
141
+                        if ($pos !== false) {
142
+                            $pathWithoutExt = substr($path, 0, $pos);
143
+                            $file = $this->getDriveFile($pathWithoutExt);
144
+                            if ($file && $this->isGoogleDocFile($file)) {
145
+                                // Switch cached Google_Service_Drive_DriveFile to the correct index
146
+                                unset($this->driveFiles[$pathWithoutExt]);
147
+                                $this->driveFiles[$path] = $file;
148
+                                $parentId = $file->getId();
149
+                            } else {
150
+                                return false;
151
+                            }
152
+                        } else {
153
+                            return false;
154
+                        }
155
+                    }
156
+                }
157
+            }
158
+            return $this->driveFiles[$path];
159
+        }
160
+    }
161
+
162
+    /**
163
+     * Set the Google_Service_Drive_DriveFile object in the cache
164
+     * @param string $path
165
+     * @param \Google_Service_Drive_DriveFile|false $file
166
+     */
167
+    private function setDriveFile($path, $file) {
168
+        $path = trim($path, '/');
169
+        $this->driveFiles[$path] = $file;
170
+        if ($file === false) {
171
+            // Remove all children
172
+            $len = strlen($path);
173
+            foreach ($this->driveFiles as $key => $file) {
174
+                if (substr($key, 0, $len) === $path) {
175
+                    unset($this->driveFiles[$key]);
176
+                }
177
+            }
178
+        }
179
+    }
180
+
181
+    /**
182
+     * Write a log message to inform about duplicate file names
183
+     * @param string $path
184
+     */
185
+    private function onDuplicateFileDetected($path) {
186
+        $about = $this->service->about->get();
187
+        $user = $about->getName();
188
+        \OCP\Util::writeLog('files_external',
189
+            'Ignoring duplicate file name: '.$path.' on Google Drive for Google user: '.$user,
190
+            \OCP\Util::INFO
191
+        );
192
+    }
193
+
194
+    /**
195
+     * Generate file extension for a Google Doc, choosing Open Document formats for download
196
+     * @param string $mimetype
197
+     * @return string
198
+     */
199
+    private function getGoogleDocExtension($mimetype) {
200
+        if ($mimetype === self::DOCUMENT) {
201
+            return 'odt';
202
+        } else if ($mimetype === self::SPREADSHEET) {
203
+            return 'ods';
204
+        } else if ($mimetype === self::DRAWING) {
205
+            return 'jpg';
206
+        } else if ($mimetype === self::PRESENTATION) {
207
+            // Download as .odp is not available
208
+            return 'pdf';
209
+        } else {
210
+            return '';
211
+        }
212
+    }
213
+
214
+    /**
215
+     * Returns whether the given drive file is a Google Doc file
216
+     *
217
+     * @param \Google_Service_Drive_DriveFile
218
+     *
219
+     * @return true if the file is a Google Doc file, false otherwise
220
+     */
221
+    private function isGoogleDocFile($file) {
222
+        return $this->getGoogleDocExtension($file->getMimeType()) !== '';
223
+    }
224
+
225
+    public function mkdir($path) {
226
+        if (!$this->is_dir($path)) {
227
+            $parentFolder = $this->getDriveFile(dirname($path));
228
+            if ($parentFolder) {
229
+                $folder = new \Google_Service_Drive_DriveFile();
230
+                $folder->setTitle(basename($path));
231
+                $folder->setMimeType(self::FOLDER);
232
+                $parent = new \Google_Service_Drive_ParentReference();
233
+                $parent->setId($parentFolder->getId());
234
+                $folder->setParents(array($parent));
235
+                $result = $this->service->files->insert($folder);
236
+                if ($result) {
237
+                    $this->setDriveFile($path, $result);
238
+                }
239
+                return (bool)$result;
240
+            }
241
+        }
242
+        return false;
243
+    }
244
+
245
+    public function rmdir($path) {
246
+        if (!$this->isDeletable($path)) {
247
+            return false;
248
+        }
249
+        if (trim($path, '/') === '') {
250
+            $dir = $this->opendir($path);
251
+            if(is_resource($dir)) {
252
+                while (($file = readdir($dir)) !== false) {
253
+                    if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
254
+                        if (!$this->unlink($path.'/'.$file)) {
255
+                            return false;
256
+                        }
257
+                    }
258
+                }
259
+                closedir($dir);
260
+            }
261
+            $this->driveFiles = array();
262
+            return true;
263
+        } else {
264
+            return $this->unlink($path);
265
+        }
266
+    }
267
+
268
+    public function opendir($path) {
269
+        $folder = $this->getDriveFile($path);
270
+        if ($folder) {
271
+            $files = array();
272
+            $duplicates = array();
273
+            $pageToken = true;
274
+            while ($pageToken) {
275
+                $params = array();
276
+                if ($pageToken !== true) {
277
+                    $params['pageToken'] = $pageToken;
278
+                }
279
+                $params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false";
280
+                $children = $this->service->files->listFiles($params);
281
+                foreach ($children->getItems() as $child) {
282
+                    $name = $child->getTitle();
283
+                    // Check if this is a Google Doc i.e. no extension in name
284
+                    $extension = $child->getFileExtension();
285
+                    if (empty($extension)) {
286
+                        if ($child->getMimeType() === self::MAP) {
287
+                            continue; // No method known to transfer map files, ignore it
288
+                        } else if ($child->getMimeType() !== self::FOLDER) {
289
+                            $name .= '.'.$this->getGoogleDocExtension($child->getMimeType());
290
+                        }
291
+                    }
292
+                    if ($path === '') {
293
+                        $filepath = $name;
294
+                    } else {
295
+                        $filepath = $path.'/'.$name;
296
+                    }
297
+                    // Google Drive allows files with the same name, ownCloud doesn't
298
+                    // Prevent opendir() from returning any duplicate files
299
+                    $key = array_search($name, $files);
300
+                    if ($key !== false || isset($duplicates[$filepath])) {
301
+                        if (!isset($duplicates[$filepath])) {
302
+                            $duplicates[$filepath] = true;
303
+                            $this->setDriveFile($filepath, false);
304
+                            unset($files[$key]);
305
+                            $this->onDuplicateFileDetected($filepath);
306
+                        }
307
+                    } else {
308
+                        // Cache the Google_Service_Drive_DriveFile for future use
309
+                        $this->setDriveFile($filepath, $child);
310
+                        $files[] = $name;
311
+                    }
312
+                }
313
+                $pageToken = $children->getNextPageToken();
314
+            }
315
+            return IteratorDirectory::wrap($files);
316
+        } else {
317
+            return false;
318
+        }
319
+    }
320
+
321
+    public function stat($path) {
322
+        $file = $this->getDriveFile($path);
323
+        if ($file) {
324
+            $stat = array();
325
+            if ($this->filetype($path) === 'dir') {
326
+                $stat['size'] = 0;
327
+            } else {
328
+                // Check if this is a Google Doc
329
+                if ($this->isGoogleDocFile($file)) {
330
+                    // Return unknown file size
331
+                    $stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN;
332
+                } else {
333
+                    $stat['size'] = $file->getFileSize();
334
+                }
335
+            }
336
+            $stat['atime'] = strtotime($file->getLastViewedByMeDate());
337
+            $stat['mtime'] = strtotime($file->getModifiedDate());
338
+            $stat['ctime'] = strtotime($file->getCreatedDate());
339
+            return $stat;
340
+        } else {
341
+            return false;
342
+        }
343
+    }
344
+
345
+    public function filetype($path) {
346
+        if ($path === '') {
347
+            return 'dir';
348
+        } else {
349
+            $file = $this->getDriveFile($path);
350
+            if ($file) {
351
+                if ($file->getMimeType() === self::FOLDER) {
352
+                    return 'dir';
353
+                } else {
354
+                    return 'file';
355
+                }
356
+            } else {
357
+                return false;
358
+            }
359
+        }
360
+    }
361
+
362
+    public function isUpdatable($path) {
363
+        $file = $this->getDriveFile($path);
364
+        if ($file) {
365
+            return $file->getEditable();
366
+        } else {
367
+            return false;
368
+        }
369
+    }
370
+
371
+    public function file_exists($path) {
372
+        return (bool)$this->getDriveFile($path);
373
+    }
374
+
375
+    public function unlink($path) {
376
+        $file = $this->getDriveFile($path);
377
+        if ($file) {
378
+            $result = $this->service->files->trash($file->getId());
379
+            if ($result) {
380
+                $this->setDriveFile($path, false);
381
+            }
382
+            return (bool)$result;
383
+        } else {
384
+            return false;
385
+        }
386
+    }
387
+
388
+    public function rename($path1, $path2) {
389
+        $file = $this->getDriveFile($path1);
390
+        if ($file) {
391
+            $newFile = $this->getDriveFile($path2);
392
+            if (dirname($path1) === dirname($path2)) {
393
+                if ($newFile) {
394
+                    // rename to the name of the target file, could be an office file without extension
395
+                    $file->setTitle($newFile->getTitle());
396
+                } else {
397
+                    $file->setTitle(basename(($path2)));
398
+                }
399
+            } else {
400
+                // Change file parent
401
+                $parentFolder2 = $this->getDriveFile(dirname($path2));
402
+                if ($parentFolder2) {
403
+                    $parent = new \Google_Service_Drive_ParentReference();
404
+                    $parent->setId($parentFolder2->getId());
405
+                    $file->setParents(array($parent));
406
+                } else {
407
+                    return false;
408
+                }
409
+            }
410
+            // We need to get the object for the existing file with the same
411
+            // name (if there is one) before we do the patch. If oldfile
412
+            // exists and is a directory we have to delete it before we
413
+            // do the rename too.
414
+            $oldfile = $this->getDriveFile($path2);
415
+            if ($oldfile && $this->is_dir($path2)) {
416
+                $this->rmdir($path2);
417
+                $oldfile = false;
418
+            }
419
+            $result = $this->service->files->patch($file->getId(), $file);
420
+            if ($result) {
421
+                $this->setDriveFile($path1, false);
422
+                $this->setDriveFile($path2, $result);
423
+                if ($oldfile && $newFile) {
424
+                    // only delete if they have a different id (same id can happen for part files)
425
+                    if ($newFile->getId() !== $oldfile->getId()) {
426
+                        $this->service->files->delete($oldfile->getId());
427
+                    }
428
+                }
429
+            }
430
+            return (bool)$result;
431
+        } else {
432
+            return false;
433
+        }
434
+    }
435
+
436
+    public function fopen($path, $mode) {
437
+        $pos = strrpos($path, '.');
438
+        if ($pos !== false) {
439
+            $ext = substr($path, $pos);
440
+        } else {
441
+            $ext = '';
442
+        }
443
+        switch ($mode) {
444
+            case 'r':
445
+            case 'rb':
446
+                $file = $this->getDriveFile($path);
447
+                if ($file) {
448
+                    $exportLinks = $file->getExportLinks();
449
+                    $mimetype = $this->getMimeType($path);
450
+                    $downloadUrl = null;
451
+                    if ($exportLinks && isset($exportLinks[$mimetype])) {
452
+                        $downloadUrl = $exportLinks[$mimetype];
453
+                    } else {
454
+                        $downloadUrl = $file->getDownloadUrl();
455
+                    }
456
+                    if (isset($downloadUrl)) {
457
+                        $request = new \Google_Http_Request($downloadUrl, 'GET', null, null);
458
+                        $httpRequest = $this->client->getAuth()->sign($request);
459
+                        // the library's service doesn't support streaming, so we use Guzzle instead
460
+                        $client = \OC::$server->getHTTPClientService()->newClient();
461
+                        try {
462
+                            $response = $client->get($downloadUrl, [
463
+                                'headers' => $httpRequest->getRequestHeaders(),
464
+                                'stream' => true,
465
+                                'verify' => realpath(__DIR__ . '/../../../3rdparty/google-api-php-client/src/Google/IO/cacerts.pem'),
466
+                            ]);
467
+                        } catch (RequestException $e) {
468
+                            if(!is_null($e->getResponse())) {
469
+                                if ($e->getResponse()->getStatusCode() === 404) {
470
+                                    return false;
471
+                                } else {
472
+                                    throw $e;
473
+                                }
474
+                            } else {
475
+                                throw $e;
476
+                            }
477
+                        }
478
+
479
+                        $handle = $response->getBody();
480
+                        return RetryWrapper::wrap($handle);
481
+                    }
482
+                }
483
+                return false;
484
+            case 'w':
485
+            case 'wb':
486
+            case 'a':
487
+            case 'ab':
488
+            case 'r+':
489
+            case 'w+':
490
+            case 'wb+':
491
+            case 'a+':
492
+            case 'x':
493
+            case 'x+':
494
+            case 'c':
495
+            case 'c+':
496
+                $tmpFile = \OCP\Files::tmpFile($ext);
497
+                if ($this->file_exists($path)) {
498
+                    $source = $this->fopen($path, 'rb');
499
+                    file_put_contents($tmpFile, $source);
500
+                }
501
+                $handle = fopen($tmpFile, $mode);
502
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
503
+                    $this->writeBack($tmpFile, $path);
504
+                });
505
+        }
506
+    }
507
+
508
+    public function writeBack($tmpFile, $path) {
509
+        $parentFolder = $this->getDriveFile(dirname($path));
510
+        if ($parentFolder) {
511
+            $mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
512
+            $params = array(
513
+                'mimeType' => $mimetype,
514
+                'uploadType' => 'media'
515
+            );
516
+            $result = false;
517
+
518
+            $chunkSizeBytes = 10 * 1024 * 1024;
519
+
520
+            $useChunking = false;
521
+            $size = filesize($tmpFile);
522
+            if ($size > $chunkSizeBytes) {
523
+                $useChunking = true;
524
+            } else {
525
+                $params['data'] = file_get_contents($tmpFile);
526
+            }
527
+
528
+            if ($this->file_exists($path)) {
529
+                $file = $this->getDriveFile($path);
530
+                $this->client->setDefer($useChunking);
531
+                $request = $this->service->files->update($file->getId(), $file, $params);
532
+            } else {
533
+                $file = new \Google_Service_Drive_DriveFile();
534
+                $file->setTitle(basename($path));
535
+                $file->setMimeType($mimetype);
536
+                $parent = new \Google_Service_Drive_ParentReference();
537
+                $parent->setId($parentFolder->getId());
538
+                $file->setParents(array($parent));
539
+                $this->client->setDefer($useChunking);
540
+                $request = $this->service->files->insert($file, $params);
541
+            }
542
+
543
+            if ($useChunking) {
544
+                // Create a media file upload to represent our upload process.
545
+                $media = new \Google_Http_MediaFileUpload(
546
+                    $this->client,
547
+                    $request,
548
+                    'text/plain',
549
+                    null,
550
+                    true,
551
+                    $chunkSizeBytes
552
+                );
553
+                $media->setFileSize($size);
554
+
555
+                // Upload the various chunks. $status will be false until the process is
556
+                // complete.
557
+                $status = false;
558
+                $handle = fopen($tmpFile, 'rb');
559
+                while (!$status && !feof($handle)) {
560
+                    $chunk = fread($handle, $chunkSizeBytes);
561
+                    $status = $media->nextChunk($chunk);
562
+                }
563
+
564
+                // The final value of $status will be the data from the API for the object
565
+                // that has been uploaded.
566
+                $result = false;
567
+                if ($status !== false) {
568
+                    $result = $status;
569
+                }
570
+
571
+                fclose($handle);
572
+            } else {
573
+                $result = $request;
574
+            }
575
+
576
+            // Reset to the client to execute requests immediately in the future.
577
+            $this->client->setDefer(false);
578
+
579
+            if ($result) {
580
+                $this->setDriveFile($path, $result);
581
+            }
582
+        }
583
+    }
584
+
585
+    public function getMimeType($path) {
586
+        $file = $this->getDriveFile($path);
587
+        if ($file) {
588
+            $mimetype = $file->getMimeType();
589
+            // Convert Google Doc mimetypes, choosing Open Document formats for download
590
+            if ($mimetype === self::FOLDER) {
591
+                return 'httpd/unix-directory';
592
+            } else if ($mimetype === self::DOCUMENT) {
593
+                return 'application/vnd.oasis.opendocument.text';
594
+            } else if ($mimetype === self::SPREADSHEET) {
595
+                return 'application/x-vnd.oasis.opendocument.spreadsheet';
596
+            } else if ($mimetype === self::DRAWING) {
597
+                return 'image/jpeg';
598
+            } else if ($mimetype === self::PRESENTATION) {
599
+                // Download as .odp is not available
600
+                return 'application/pdf';
601
+            } else {
602
+                // use extension-based detection, could be an encrypted file
603
+                return parent::getMimeType($path);
604
+            }
605
+        } else {
606
+            return false;
607
+        }
608
+    }
609
+
610
+    public function free_space($path) {
611
+        $about = $this->service->about->get();
612
+        return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
613
+    }
614
+
615
+    public function touch($path, $mtime = null) {
616
+        $file = $this->getDriveFile($path);
617
+        $result = false;
618
+        if ($file) {
619
+            if (isset($mtime)) {
620
+                // This is just RFC3339, but frustratingly, GDrive's API *requires*
621
+                // the fractions portion be present, while no handy PHP constant
622
+                // for RFC3339 or ISO8601 includes it. So we do it ourselves.
623
+                $file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime));
624
+                $result = $this->service->files->patch($file->getId(), $file, array(
625
+                    'setModifiedDate' => true,
626
+                ));
627
+            } else {
628
+                $result = $this->service->files->touch($file->getId());
629
+            }
630
+        } else {
631
+            $parentFolder = $this->getDriveFile(dirname($path));
632
+            if ($parentFolder) {
633
+                $file = new \Google_Service_Drive_DriveFile();
634
+                $file->setTitle(basename($path));
635
+                $parent = new \Google_Service_Drive_ParentReference();
636
+                $parent->setId($parentFolder->getId());
637
+                $file->setParents(array($parent));
638
+                $result = $this->service->files->insert($file);
639
+            }
640
+        }
641
+        if ($result) {
642
+            $this->setDriveFile($path, $result);
643
+        }
644
+        return (bool)$result;
645
+    }
646
+
647
+    public function test() {
648
+        if ($this->free_space('')) {
649
+            return true;
650
+        }
651
+        return false;
652
+    }
653
+
654
+    public function hasUpdated($path, $time) {
655
+        $appConfig = \OC::$server->getAppConfig();
656
+        if ($this->is_file($path)) {
657
+            return parent::hasUpdated($path, $time);
658
+        } else {
659
+            // Google Drive doesn't change modified times of folders when files inside are updated
660
+            // Instead we use the Changes API to see if folders have been updated, and it's a pain
661
+            $folder = $this->getDriveFile($path);
662
+            if ($folder) {
663
+                $result = false;
664
+                $folderId = $folder->getId();
665
+                $startChangeId = $appConfig->getValue('files_external', $this->getId().'cId');
666
+                $params = array(
667
+                    'includeDeleted' => true,
668
+                    'includeSubscribed' => true,
669
+                );
670
+                if (isset($startChangeId)) {
671
+                    $startChangeId = (int)$startChangeId;
672
+                    $largestChangeId = $startChangeId;
673
+                    $params['startChangeId'] = $startChangeId + 1;
674
+                } else {
675
+                    $largestChangeId = 0;
676
+                }
677
+                $pageToken = true;
678
+                while ($pageToken) {
679
+                    if ($pageToken !== true) {
680
+                        $params['pageToken'] = $pageToken;
681
+                    }
682
+                    $changes = $this->service->changes->listChanges($params);
683
+                    if ($largestChangeId === 0 || $largestChangeId === $startChangeId) {
684
+                        $largestChangeId = $changes->getLargestChangeId();
685
+                    }
686
+                    if (isset($startChangeId)) {
687
+                        // Check if a file in this folder has been updated
688
+                        // There is no way to filter by folder at the API level...
689
+                        foreach ($changes->getItems() as $change) {
690
+                            $file = $change->getFile();
691
+                            if ($file) {
692
+                                foreach ($file->getParents() as $parent) {
693
+                                    if ($parent->getId() === $folderId) {
694
+                                        $result = true;
695
+                                    // Check if there are changes in different folders
696
+                                    } else if ($change->getId() <= $largestChangeId) {
697
+                                        // Decrement id so this change is fetched when called again
698
+                                        $largestChangeId = $change->getId();
699
+                                        $largestChangeId--;
700
+                                    }
701
+                                }
702
+                            }
703
+                        }
704
+                        $pageToken = $changes->getNextPageToken();
705
+                    } else {
706
+                        // Assuming the initial scan just occurred and changes are negligible
707
+                        break;
708
+                    }
709
+                }
710
+                $appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId);
711
+                return $result;
712
+            }
713
+        }
714
+        return false;
715
+    }
716
+
717
+    /**
718
+     * check if curl is installed
719
+     */
720
+    public static function checkDependencies() {
721
+        return true;
722
+    }
723 723
 
724 724
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/SFTP.php 1 patch
Indentation   +424 added lines, -424 removed lines patch added patch discarded remove patch
@@ -42,428 +42,428 @@
 block discarded – undo
42 42
 * provide access to SFTP servers.
43 43
 */
44 44
 class SFTP extends \OC\Files\Storage\Common {
45
-	private $host;
46
-	private $user;
47
-	private $root;
48
-	private $port = 22;
49
-
50
-	private $auth;
51
-
52
-	/**
53
-	 * @var \phpseclib\Net\SFTP
54
-	 */
55
-	protected $client;
56
-
57
-	/**
58
-	 * @param string $host protocol://server:port
59
-	 * @return array [$server, $port]
60
-	 */
61
-	private function splitHost($host) {
62
-		$input = $host;
63
-		if (strpos($host, '://') === false) {
64
-			// add a protocol to fix parse_url behavior with ipv6
65
-			$host = 'http://' . $host;
66
-		}
67
-
68
-		$parsed = parse_url($host);
69
-		if(is_array($parsed) && isset($parsed['port'])) {
70
-			return [$parsed['host'], $parsed['port']];
71
-		} else if (is_array($parsed)) {
72
-			return [$parsed['host'], 22];
73
-		} else {
74
-			return [$input, 22];
75
-		}
76
-	}
77
-
78
-	/**
79
-	 * {@inheritdoc}
80
-	 */
81
-	public function __construct($params) {
82
-		// Register sftp://
83
-		Stream::register();
84
-
85
-		$parsedHost =  $this->splitHost($params['host']);
86
-
87
-		$this->host = $parsedHost[0];
88
-		$this->port = $parsedHost[1];
89
-
90
-		if (!isset($params['user'])) {
91
-			throw new \UnexpectedValueException('no authentication parameters specified');
92
-		}
93
-		$this->user = $params['user'];
94
-
95
-		if (isset($params['public_key_auth'])) {
96
-			$this->auth = $params['public_key_auth'];
97
-		} elseif (isset($params['password'])) {
98
-			$this->auth = $params['password'];
99
-		} else {
100
-			throw new \UnexpectedValueException('no authentication parameters specified');
101
-		}
102
-
103
-		$this->root
104
-			= isset($params['root']) ? $this->cleanPath($params['root']) : '/';
105
-
106
-		if ($this->root[0] != '/') {
107
-			 $this->root = '/' . $this->root;
108
-		}
109
-
110
-		if (substr($this->root, -1, 1) != '/') {
111
-			$this->root .= '/';
112
-		}
113
-	}
114
-
115
-	/**
116
-	 * Returns the connection.
117
-	 *
118
-	 * @return \phpseclib\Net\SFTP connected client instance
119
-	 * @throws \Exception when the connection failed
120
-	 */
121
-	public function getConnection() {
122
-		if (!is_null($this->client)) {
123
-			return $this->client;
124
-		}
125
-
126
-		$hostKeys = $this->readHostKeys();
127
-		$this->client = new \phpseclib\Net\SFTP($this->host, $this->port);
128
-
129
-		// The SSH Host Key MUST be verified before login().
130
-		$currentHostKey = $this->client->getServerPublicHostKey();
131
-		if (array_key_exists($this->host, $hostKeys)) {
132
-			if ($hostKeys[$this->host] != $currentHostKey) {
133
-				throw new \Exception('Host public key does not match known key');
134
-			}
135
-		} else {
136
-			$hostKeys[$this->host] = $currentHostKey;
137
-			$this->writeHostKeys($hostKeys);
138
-		}
139
-
140
-		if (!$this->client->login($this->user, $this->auth)) {
141
-			throw new \Exception('Login failed');
142
-		}
143
-		return $this->client;
144
-	}
145
-
146
-	/**
147
-	 * {@inheritdoc}
148
-	 */
149
-	public function test() {
150
-		if (
151
-			!isset($this->host)
152
-			|| !isset($this->user)
153
-		) {
154
-			return false;
155
-		}
156
-		return $this->getConnection()->nlist() !== false;
157
-	}
158
-
159
-	/**
160
-	 * {@inheritdoc}
161
-	 */
162
-	public function getId(){
163
-		$id = 'sftp::' . $this->user . '@' . $this->host;
164
-		if ($this->port !== 22) {
165
-			$id .= ':' . $this->port;
166
-		}
167
-		// note: this will double the root slash,
168
-		// we should not change it to keep compatible with
169
-		// old storage ids
170
-		$id .= '/' . $this->root;
171
-		return $id;
172
-	}
173
-
174
-	/**
175
-	 * @return string
176
-	 */
177
-	public function getHost() {
178
-		return $this->host;
179
-	}
180
-
181
-	/**
182
-	 * @return string
183
-	 */
184
-	public function getRoot() {
185
-		return $this->root;
186
-	}
187
-
188
-	/**
189
-	 * @return mixed
190
-	 */
191
-	public function getUser() {
192
-		return $this->user;
193
-	}
194
-
195
-	/**
196
-	 * @param string $path
197
-	 * @return string
198
-	 */
199
-	private function absPath($path) {
200
-		return $this->root . $this->cleanPath($path);
201
-	}
202
-
203
-	/**
204
-	 * @return string|false
205
-	 */
206
-	private function hostKeysPath() {
207
-		try {
208
-			$storage_view = \OCP\Files::getStorage('files_external');
209
-			if ($storage_view) {
210
-				return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') .
211
-					$storage_view->getAbsolutePath('') .
212
-					'ssh_hostKeys';
213
-			}
214
-		} catch (\Exception $e) {
215
-		}
216
-		return false;
217
-	}
218
-
219
-	/**
220
-	 * @param $keys
221
-	 * @return bool
222
-	 */
223
-	protected function writeHostKeys($keys) {
224
-		try {
225
-			$keyPath = $this->hostKeysPath();
226
-			if ($keyPath && file_exists($keyPath)) {
227
-				$fp = fopen($keyPath, 'w');
228
-				foreach ($keys as $host => $key) {
229
-					fwrite($fp, $host . '::' . $key . "\n");
230
-				}
231
-				fclose($fp);
232
-				return true;
233
-			}
234
-		} catch (\Exception $e) {
235
-		}
236
-		return false;
237
-	}
238
-
239
-	/**
240
-	 * @return array
241
-	 */
242
-	protected function readHostKeys() {
243
-		try {
244
-			$keyPath = $this->hostKeysPath();
245
-			if (file_exists($keyPath)) {
246
-				$hosts = array();
247
-				$keys = array();
248
-				$lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
249
-				if ($lines) {
250
-					foreach ($lines as $line) {
251
-						$hostKeyArray = explode("::", $line, 2);
252
-						if (count($hostKeyArray) == 2) {
253
-							$hosts[] = $hostKeyArray[0];
254
-							$keys[] = $hostKeyArray[1];
255
-						}
256
-					}
257
-					return array_combine($hosts, $keys);
258
-				}
259
-			}
260
-		} catch (\Exception $e) {
261
-		}
262
-		return array();
263
-	}
264
-
265
-	/**
266
-	 * {@inheritdoc}
267
-	 */
268
-	public function mkdir($path) {
269
-		try {
270
-			return $this->getConnection()->mkdir($this->absPath($path));
271
-		} catch (\Exception $e) {
272
-			return false;
273
-		}
274
-	}
275
-
276
-	/**
277
-	 * {@inheritdoc}
278
-	 */
279
-	public function rmdir($path) {
280
-		try {
281
-			$result = $this->getConnection()->delete($this->absPath($path), true);
282
-			// workaround: stray stat cache entry when deleting empty folders
283
-			// see https://github.com/phpseclib/phpseclib/issues/706
284
-			$this->getConnection()->clearStatCache();
285
-			return $result;
286
-		} catch (\Exception $e) {
287
-			return false;
288
-		}
289
-	}
290
-
291
-	/**
292
-	 * {@inheritdoc}
293
-	 */
294
-	public function opendir($path) {
295
-		try {
296
-			$list = $this->getConnection()->nlist($this->absPath($path));
297
-			if ($list === false) {
298
-				return false;
299
-			}
300
-
301
-			$id = md5('sftp:' . $path);
302
-			$dirStream = array();
303
-			foreach($list as $file) {
304
-				if ($file != '.' && $file != '..') {
305
-					$dirStream[] = $file;
306
-				}
307
-			}
308
-			return IteratorDirectory::wrap($dirStream);
309
-		} catch(\Exception $e) {
310
-			return false;
311
-		}
312
-	}
313
-
314
-	/**
315
-	 * {@inheritdoc}
316
-	 */
317
-	public function filetype($path) {
318
-		try {
319
-			$stat = $this->getConnection()->stat($this->absPath($path));
320
-			if ($stat['type'] == NET_SFTP_TYPE_REGULAR) {
321
-				return 'file';
322
-			}
323
-
324
-			if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
325
-				return 'dir';
326
-			}
327
-		} catch (\Exception $e) {
328
-
329
-		}
330
-		return false;
331
-	}
332
-
333
-	/**
334
-	 * {@inheritdoc}
335
-	 */
336
-	public function file_exists($path) {
337
-		try {
338
-			return $this->getConnection()->stat($this->absPath($path)) !== false;
339
-		} catch (\Exception $e) {
340
-			return false;
341
-		}
342
-	}
343
-
344
-	/**
345
-	 * {@inheritdoc}
346
-	 */
347
-	public function unlink($path) {
348
-		try {
349
-			return $this->getConnection()->delete($this->absPath($path), true);
350
-		} catch (\Exception $e) {
351
-			return false;
352
-		}
353
-	}
354
-
355
-	/**
356
-	 * {@inheritdoc}
357
-	 */
358
-	public function fopen($path, $mode) {
359
-		try {
360
-			$absPath = $this->absPath($path);
361
-			switch($mode) {
362
-				case 'r':
363
-				case 'rb':
364
-					if ( !$this->file_exists($path)) {
365
-						return false;
366
-					}
367
-				case 'w':
368
-				case 'wb':
369
-				case 'a':
370
-				case 'ab':
371
-				case 'r+':
372
-				case 'w+':
373
-				case 'wb+':
374
-				case 'a+':
375
-				case 'x':
376
-				case 'x+':
377
-				case 'c':
378
-				case 'c+':
379
-					$context = stream_context_create(array('sftp' => array('session' => $this->getConnection())));
380
-					$handle = fopen($this->constructUrl($path), $mode, false, $context);
381
-					return RetryWrapper::wrap($handle);
382
-			}
383
-		} catch (\Exception $e) {
384
-		}
385
-		return false;
386
-	}
387
-
388
-	/**
389
-	 * {@inheritdoc}
390
-	 */
391
-	public function touch($path, $mtime=null) {
392
-		try {
393
-			if (!is_null($mtime)) {
394
-				return false;
395
-			}
396
-			if (!$this->file_exists($path)) {
397
-				$this->getConnection()->put($this->absPath($path), '');
398
-			} else {
399
-				return false;
400
-			}
401
-		} catch (\Exception $e) {
402
-			return false;
403
-		}
404
-		return true;
405
-	}
406
-
407
-	/**
408
-	 * @param string $path
409
-	 * @param string $target
410
-	 * @throws \Exception
411
-	 */
412
-	public function getFile($path, $target) {
413
-		$this->getConnection()->get($path, $target);
414
-	}
415
-
416
-	/**
417
-	 * @param string $path
418
-	 * @param string $target
419
-	 * @throws \Exception
420
-	 */
421
-	public function uploadFile($path, $target) {
422
-		$this->getConnection()->put($target, $path, NET_SFTP_LOCAL_FILE);
423
-	}
424
-
425
-	/**
426
-	 * {@inheritdoc}
427
-	 */
428
-	public function rename($source, $target) {
429
-		try {
430
-			if ($this->file_exists($target)) {
431
-				$this->unlink($target);
432
-			}
433
-			return $this->getConnection()->rename(
434
-				$this->absPath($source),
435
-				$this->absPath($target)
436
-			);
437
-		} catch (\Exception $e) {
438
-			return false;
439
-		}
440
-	}
441
-
442
-	/**
443
-	 * {@inheritdoc}
444
-	 */
445
-	public function stat($path) {
446
-		try {
447
-			$stat = $this->getConnection()->stat($this->absPath($path));
448
-
449
-			$mtime = $stat ? $stat['mtime'] : -1;
450
-			$size = $stat ? $stat['size'] : 0;
451
-
452
-			return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1);
453
-		} catch (\Exception $e) {
454
-			return false;
455
-		}
456
-	}
457
-
458
-	/**
459
-	 * @param string $path
460
-	 * @return string
461
-	 */
462
-	public function constructUrl($path) {
463
-		// Do not pass the password here. We want to use the Net_SFTP object
464
-		// supplied via stream context or fail. We only supply username and
465
-		// hostname because this might show up in logs (they are not used).
466
-		$url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path;
467
-		return $url;
468
-	}
45
+    private $host;
46
+    private $user;
47
+    private $root;
48
+    private $port = 22;
49
+
50
+    private $auth;
51
+
52
+    /**
53
+     * @var \phpseclib\Net\SFTP
54
+     */
55
+    protected $client;
56
+
57
+    /**
58
+     * @param string $host protocol://server:port
59
+     * @return array [$server, $port]
60
+     */
61
+    private function splitHost($host) {
62
+        $input = $host;
63
+        if (strpos($host, '://') === false) {
64
+            // add a protocol to fix parse_url behavior with ipv6
65
+            $host = 'http://' . $host;
66
+        }
67
+
68
+        $parsed = parse_url($host);
69
+        if(is_array($parsed) && isset($parsed['port'])) {
70
+            return [$parsed['host'], $parsed['port']];
71
+        } else if (is_array($parsed)) {
72
+            return [$parsed['host'], 22];
73
+        } else {
74
+            return [$input, 22];
75
+        }
76
+    }
77
+
78
+    /**
79
+     * {@inheritdoc}
80
+     */
81
+    public function __construct($params) {
82
+        // Register sftp://
83
+        Stream::register();
84
+
85
+        $parsedHost =  $this->splitHost($params['host']);
86
+
87
+        $this->host = $parsedHost[0];
88
+        $this->port = $parsedHost[1];
89
+
90
+        if (!isset($params['user'])) {
91
+            throw new \UnexpectedValueException('no authentication parameters specified');
92
+        }
93
+        $this->user = $params['user'];
94
+
95
+        if (isset($params['public_key_auth'])) {
96
+            $this->auth = $params['public_key_auth'];
97
+        } elseif (isset($params['password'])) {
98
+            $this->auth = $params['password'];
99
+        } else {
100
+            throw new \UnexpectedValueException('no authentication parameters specified');
101
+        }
102
+
103
+        $this->root
104
+            = isset($params['root']) ? $this->cleanPath($params['root']) : '/';
105
+
106
+        if ($this->root[0] != '/') {
107
+                $this->root = '/' . $this->root;
108
+        }
109
+
110
+        if (substr($this->root, -1, 1) != '/') {
111
+            $this->root .= '/';
112
+        }
113
+    }
114
+
115
+    /**
116
+     * Returns the connection.
117
+     *
118
+     * @return \phpseclib\Net\SFTP connected client instance
119
+     * @throws \Exception when the connection failed
120
+     */
121
+    public function getConnection() {
122
+        if (!is_null($this->client)) {
123
+            return $this->client;
124
+        }
125
+
126
+        $hostKeys = $this->readHostKeys();
127
+        $this->client = new \phpseclib\Net\SFTP($this->host, $this->port);
128
+
129
+        // The SSH Host Key MUST be verified before login().
130
+        $currentHostKey = $this->client->getServerPublicHostKey();
131
+        if (array_key_exists($this->host, $hostKeys)) {
132
+            if ($hostKeys[$this->host] != $currentHostKey) {
133
+                throw new \Exception('Host public key does not match known key');
134
+            }
135
+        } else {
136
+            $hostKeys[$this->host] = $currentHostKey;
137
+            $this->writeHostKeys($hostKeys);
138
+        }
139
+
140
+        if (!$this->client->login($this->user, $this->auth)) {
141
+            throw new \Exception('Login failed');
142
+        }
143
+        return $this->client;
144
+    }
145
+
146
+    /**
147
+     * {@inheritdoc}
148
+     */
149
+    public function test() {
150
+        if (
151
+            !isset($this->host)
152
+            || !isset($this->user)
153
+        ) {
154
+            return false;
155
+        }
156
+        return $this->getConnection()->nlist() !== false;
157
+    }
158
+
159
+    /**
160
+     * {@inheritdoc}
161
+     */
162
+    public function getId(){
163
+        $id = 'sftp::' . $this->user . '@' . $this->host;
164
+        if ($this->port !== 22) {
165
+            $id .= ':' . $this->port;
166
+        }
167
+        // note: this will double the root slash,
168
+        // we should not change it to keep compatible with
169
+        // old storage ids
170
+        $id .= '/' . $this->root;
171
+        return $id;
172
+    }
173
+
174
+    /**
175
+     * @return string
176
+     */
177
+    public function getHost() {
178
+        return $this->host;
179
+    }
180
+
181
+    /**
182
+     * @return string
183
+     */
184
+    public function getRoot() {
185
+        return $this->root;
186
+    }
187
+
188
+    /**
189
+     * @return mixed
190
+     */
191
+    public function getUser() {
192
+        return $this->user;
193
+    }
194
+
195
+    /**
196
+     * @param string $path
197
+     * @return string
198
+     */
199
+    private function absPath($path) {
200
+        return $this->root . $this->cleanPath($path);
201
+    }
202
+
203
+    /**
204
+     * @return string|false
205
+     */
206
+    private function hostKeysPath() {
207
+        try {
208
+            $storage_view = \OCP\Files::getStorage('files_external');
209
+            if ($storage_view) {
210
+                return \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') .
211
+                    $storage_view->getAbsolutePath('') .
212
+                    'ssh_hostKeys';
213
+            }
214
+        } catch (\Exception $e) {
215
+        }
216
+        return false;
217
+    }
218
+
219
+    /**
220
+     * @param $keys
221
+     * @return bool
222
+     */
223
+    protected function writeHostKeys($keys) {
224
+        try {
225
+            $keyPath = $this->hostKeysPath();
226
+            if ($keyPath && file_exists($keyPath)) {
227
+                $fp = fopen($keyPath, 'w');
228
+                foreach ($keys as $host => $key) {
229
+                    fwrite($fp, $host . '::' . $key . "\n");
230
+                }
231
+                fclose($fp);
232
+                return true;
233
+            }
234
+        } catch (\Exception $e) {
235
+        }
236
+        return false;
237
+    }
238
+
239
+    /**
240
+     * @return array
241
+     */
242
+    protected function readHostKeys() {
243
+        try {
244
+            $keyPath = $this->hostKeysPath();
245
+            if (file_exists($keyPath)) {
246
+                $hosts = array();
247
+                $keys = array();
248
+                $lines = file($keyPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
249
+                if ($lines) {
250
+                    foreach ($lines as $line) {
251
+                        $hostKeyArray = explode("::", $line, 2);
252
+                        if (count($hostKeyArray) == 2) {
253
+                            $hosts[] = $hostKeyArray[0];
254
+                            $keys[] = $hostKeyArray[1];
255
+                        }
256
+                    }
257
+                    return array_combine($hosts, $keys);
258
+                }
259
+            }
260
+        } catch (\Exception $e) {
261
+        }
262
+        return array();
263
+    }
264
+
265
+    /**
266
+     * {@inheritdoc}
267
+     */
268
+    public function mkdir($path) {
269
+        try {
270
+            return $this->getConnection()->mkdir($this->absPath($path));
271
+        } catch (\Exception $e) {
272
+            return false;
273
+        }
274
+    }
275
+
276
+    /**
277
+     * {@inheritdoc}
278
+     */
279
+    public function rmdir($path) {
280
+        try {
281
+            $result = $this->getConnection()->delete($this->absPath($path), true);
282
+            // workaround: stray stat cache entry when deleting empty folders
283
+            // see https://github.com/phpseclib/phpseclib/issues/706
284
+            $this->getConnection()->clearStatCache();
285
+            return $result;
286
+        } catch (\Exception $e) {
287
+            return false;
288
+        }
289
+    }
290
+
291
+    /**
292
+     * {@inheritdoc}
293
+     */
294
+    public function opendir($path) {
295
+        try {
296
+            $list = $this->getConnection()->nlist($this->absPath($path));
297
+            if ($list === false) {
298
+                return false;
299
+            }
300
+
301
+            $id = md5('sftp:' . $path);
302
+            $dirStream = array();
303
+            foreach($list as $file) {
304
+                if ($file != '.' && $file != '..') {
305
+                    $dirStream[] = $file;
306
+                }
307
+            }
308
+            return IteratorDirectory::wrap($dirStream);
309
+        } catch(\Exception $e) {
310
+            return false;
311
+        }
312
+    }
313
+
314
+    /**
315
+     * {@inheritdoc}
316
+     */
317
+    public function filetype($path) {
318
+        try {
319
+            $stat = $this->getConnection()->stat($this->absPath($path));
320
+            if ($stat['type'] == NET_SFTP_TYPE_REGULAR) {
321
+                return 'file';
322
+            }
323
+
324
+            if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
325
+                return 'dir';
326
+            }
327
+        } catch (\Exception $e) {
328
+
329
+        }
330
+        return false;
331
+    }
332
+
333
+    /**
334
+     * {@inheritdoc}
335
+     */
336
+    public function file_exists($path) {
337
+        try {
338
+            return $this->getConnection()->stat($this->absPath($path)) !== false;
339
+        } catch (\Exception $e) {
340
+            return false;
341
+        }
342
+    }
343
+
344
+    /**
345
+     * {@inheritdoc}
346
+     */
347
+    public function unlink($path) {
348
+        try {
349
+            return $this->getConnection()->delete($this->absPath($path), true);
350
+        } catch (\Exception $e) {
351
+            return false;
352
+        }
353
+    }
354
+
355
+    /**
356
+     * {@inheritdoc}
357
+     */
358
+    public function fopen($path, $mode) {
359
+        try {
360
+            $absPath = $this->absPath($path);
361
+            switch($mode) {
362
+                case 'r':
363
+                case 'rb':
364
+                    if ( !$this->file_exists($path)) {
365
+                        return false;
366
+                    }
367
+                case 'w':
368
+                case 'wb':
369
+                case 'a':
370
+                case 'ab':
371
+                case 'r+':
372
+                case 'w+':
373
+                case 'wb+':
374
+                case 'a+':
375
+                case 'x':
376
+                case 'x+':
377
+                case 'c':
378
+                case 'c+':
379
+                    $context = stream_context_create(array('sftp' => array('session' => $this->getConnection())));
380
+                    $handle = fopen($this->constructUrl($path), $mode, false, $context);
381
+                    return RetryWrapper::wrap($handle);
382
+            }
383
+        } catch (\Exception $e) {
384
+        }
385
+        return false;
386
+    }
387
+
388
+    /**
389
+     * {@inheritdoc}
390
+     */
391
+    public function touch($path, $mtime=null) {
392
+        try {
393
+            if (!is_null($mtime)) {
394
+                return false;
395
+            }
396
+            if (!$this->file_exists($path)) {
397
+                $this->getConnection()->put($this->absPath($path), '');
398
+            } else {
399
+                return false;
400
+            }
401
+        } catch (\Exception $e) {
402
+            return false;
403
+        }
404
+        return true;
405
+    }
406
+
407
+    /**
408
+     * @param string $path
409
+     * @param string $target
410
+     * @throws \Exception
411
+     */
412
+    public function getFile($path, $target) {
413
+        $this->getConnection()->get($path, $target);
414
+    }
415
+
416
+    /**
417
+     * @param string $path
418
+     * @param string $target
419
+     * @throws \Exception
420
+     */
421
+    public function uploadFile($path, $target) {
422
+        $this->getConnection()->put($target, $path, NET_SFTP_LOCAL_FILE);
423
+    }
424
+
425
+    /**
426
+     * {@inheritdoc}
427
+     */
428
+    public function rename($source, $target) {
429
+        try {
430
+            if ($this->file_exists($target)) {
431
+                $this->unlink($target);
432
+            }
433
+            return $this->getConnection()->rename(
434
+                $this->absPath($source),
435
+                $this->absPath($target)
436
+            );
437
+        } catch (\Exception $e) {
438
+            return false;
439
+        }
440
+    }
441
+
442
+    /**
443
+     * {@inheritdoc}
444
+     */
445
+    public function stat($path) {
446
+        try {
447
+            $stat = $this->getConnection()->stat($this->absPath($path));
448
+
449
+            $mtime = $stat ? $stat['mtime'] : -1;
450
+            $size = $stat ? $stat['size'] : 0;
451
+
452
+            return array('mtime' => $mtime, 'size' => $size, 'ctime' => -1);
453
+        } catch (\Exception $e) {
454
+            return false;
455
+        }
456
+    }
457
+
458
+    /**
459
+     * @param string $path
460
+     * @return string
461
+     */
462
+    public function constructUrl($path) {
463
+        // Do not pass the password here. We want to use the Net_SFTP object
464
+        // supplied via stream context or fail. We only supply username and
465
+        // hostname because this might show up in logs (they are not used).
466
+        $url = 'sftp://' . urlencode($this->user) . '@' . $this->host . ':' . $this->port . $this->root . $path;
467
+        return $url;
468
+    }
469 469
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/FTP.php 1 patch
Indentation   +109 added lines, -109 removed lines patch added patch discarded remove patch
@@ -37,122 +37,122 @@
 block discarded – undo
37 37
 use Icewind\Streams\RetryWrapper;
38 38
 
39 39
 class FTP extends StreamWrapper{
40
-	private $password;
41
-	private $user;
42
-	private $host;
43
-	private $secure;
44
-	private $root;
40
+    private $password;
41
+    private $user;
42
+    private $host;
43
+    private $secure;
44
+    private $root;
45 45
 
46
-	private static $tempFiles=array();
46
+    private static $tempFiles=array();
47 47
 
48
-	public function __construct($params) {
49
-		if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
50
-			$this->host=$params['host'];
51
-			$this->user=$params['user'];
52
-			$this->password=$params['password'];
53
-			if (isset($params['secure'])) {
54
-				$this->secure = $params['secure'];
55
-			} else {
56
-				$this->secure = false;
57
-			}
58
-			$this->root=isset($params['root'])?$params['root']:'/';
59
-			if ( ! $this->root || $this->root[0]!='/') {
60
-				$this->root='/'.$this->root;
61
-			}
62
-			if (substr($this->root, -1) !== '/') {
63
-				$this->root .= '/';
64
-			}
65
-		} else {
66
-			throw new \Exception('Creating FTP storage failed');
67
-		}
48
+    public function __construct($params) {
49
+        if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
50
+            $this->host=$params['host'];
51
+            $this->user=$params['user'];
52
+            $this->password=$params['password'];
53
+            if (isset($params['secure'])) {
54
+                $this->secure = $params['secure'];
55
+            } else {
56
+                $this->secure = false;
57
+            }
58
+            $this->root=isset($params['root'])?$params['root']:'/';
59
+            if ( ! $this->root || $this->root[0]!='/') {
60
+                $this->root='/'.$this->root;
61
+            }
62
+            if (substr($this->root, -1) !== '/') {
63
+                $this->root .= '/';
64
+            }
65
+        } else {
66
+            throw new \Exception('Creating FTP storage failed');
67
+        }
68 68
 		
69
-	}
69
+    }
70 70
 
71
-	public function getId(){
72
-		return 'ftp::' . $this->user . '@' . $this->host . '/' . $this->root;
73
-	}
71
+    public function getId(){
72
+        return 'ftp::' . $this->user . '@' . $this->host . '/' . $this->root;
73
+    }
74 74
 
75
-	/**
76
-	 * construct the ftp url
77
-	 * @param string $path
78
-	 * @return string
79
-	 */
80
-	public function constructUrl($path) {
81
-		$url='ftp';
82
-		if ($this->secure) {
83
-			$url.='s';
84
-		}
85
-		$url.='://'.urlencode($this->user).':'.urlencode($this->password).'@'.$this->host.$this->root.$path;
86
-		return $url;
87
-	}
75
+    /**
76
+     * construct the ftp url
77
+     * @param string $path
78
+     * @return string
79
+     */
80
+    public function constructUrl($path) {
81
+        $url='ftp';
82
+        if ($this->secure) {
83
+            $url.='s';
84
+        }
85
+        $url.='://'.urlencode($this->user).':'.urlencode($this->password).'@'.$this->host.$this->root.$path;
86
+        return $url;
87
+    }
88 88
 
89
-	/**
90
-	 * Unlinks file or directory
91
-	 * @param string $path
92
-	 */
93
-	public function unlink($path) {
94
-		if ($this->is_dir($path)) {
95
-			return $this->rmdir($path);
96
-		}
97
-		else {
98
-			$url = $this->constructUrl($path);
99
-			$result = unlink($url);
100
-			clearstatcache(true, $url);
101
-			return $result;
102
-		}
103
-	}
104
-	public function fopen($path,$mode) {
105
-		switch($mode) {
106
-			case 'r':
107
-			case 'rb':
108
-			case 'w':
109
-			case 'wb':
110
-			case 'a':
111
-			case 'ab':
112
-				//these are supported by the wrapper
113
-				$context = stream_context_create(array('ftp' => array('overwrite' => true)));
114
-				$handle = fopen($this->constructUrl($path), $mode, false, $context);
115
-				return RetryWrapper::wrap($handle);
116
-			case 'r+':
117
-			case 'w+':
118
-			case 'wb+':
119
-			case 'a+':
120
-			case 'x':
121
-			case 'x+':
122
-			case 'c':
123
-			case 'c+':
124
-				//emulate these
125
-				if (strrpos($path, '.')!==false) {
126
-					$ext=substr($path, strrpos($path, '.'));
127
-				} else {
128
-					$ext='';
129
-				}
130
-				$tmpFile=\OCP\Files::tmpFile($ext);
131
-				if ($this->file_exists($path)) {
132
-					$this->getFile($path, $tmpFile);
133
-				}
134
-				$handle = fopen($tmpFile, $mode);
135
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
136
-					$this->writeBack($tmpFile, $path);
137
-				});
138
-		}
139
-		return false;
140
-	}
89
+    /**
90
+     * Unlinks file or directory
91
+     * @param string $path
92
+     */
93
+    public function unlink($path) {
94
+        if ($this->is_dir($path)) {
95
+            return $this->rmdir($path);
96
+        }
97
+        else {
98
+            $url = $this->constructUrl($path);
99
+            $result = unlink($url);
100
+            clearstatcache(true, $url);
101
+            return $result;
102
+        }
103
+    }
104
+    public function fopen($path,$mode) {
105
+        switch($mode) {
106
+            case 'r':
107
+            case 'rb':
108
+            case 'w':
109
+            case 'wb':
110
+            case 'a':
111
+            case 'ab':
112
+                //these are supported by the wrapper
113
+                $context = stream_context_create(array('ftp' => array('overwrite' => true)));
114
+                $handle = fopen($this->constructUrl($path), $mode, false, $context);
115
+                return RetryWrapper::wrap($handle);
116
+            case 'r+':
117
+            case 'w+':
118
+            case 'wb+':
119
+            case 'a+':
120
+            case 'x':
121
+            case 'x+':
122
+            case 'c':
123
+            case 'c+':
124
+                //emulate these
125
+                if (strrpos($path, '.')!==false) {
126
+                    $ext=substr($path, strrpos($path, '.'));
127
+                } else {
128
+                    $ext='';
129
+                }
130
+                $tmpFile=\OCP\Files::tmpFile($ext);
131
+                if ($this->file_exists($path)) {
132
+                    $this->getFile($path, $tmpFile);
133
+                }
134
+                $handle = fopen($tmpFile, $mode);
135
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
136
+                    $this->writeBack($tmpFile, $path);
137
+                });
138
+        }
139
+        return false;
140
+    }
141 141
 
142
-	public function writeBack($tmpFile, $path) {
143
-		$this->uploadFile($tmpFile, $path);
144
-		unlink($tmpFile);
145
-	}
142
+    public function writeBack($tmpFile, $path) {
143
+        $this->uploadFile($tmpFile, $path);
144
+        unlink($tmpFile);
145
+    }
146 146
 
147
-	/**
148
-	 * check if php-ftp is installed
149
-	 */
150
-	public static function checkDependencies() {
151
-		if (function_exists('ftp_login')) {
152
-			return(true);
153
-		} else {
154
-			return array('ftp');
155
-		}
156
-	}
147
+    /**
148
+     * check if php-ftp is installed
149
+     */
150
+    public static function checkDependencies() {
151
+        if (function_exists('ftp_login')) {
152
+            return(true);
153
+        } else {
154
+            return array('ftp');
155
+        }
156
+    }
157 157
 
158 158
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/StreamWrapper.php 1 patch
Indentation   +96 added lines, -96 removed lines patch added patch discarded remove patch
@@ -30,101 +30,101 @@
 block discarded – undo
30 30
 
31 31
 abstract class StreamWrapper extends \OC\Files\Storage\Common {
32 32
 
33
-	/**
34
-	 * @param string $path
35
-	 * @return string|null
36
-	 */
37
-	abstract public function constructUrl($path);
38
-
39
-	public function mkdir($path) {
40
-		return mkdir($this->constructUrl($path));
41
-	}
42
-
43
-	public function rmdir($path) {
44
-		if ($this->is_dir($path) && $this->isDeletable($path)) {
45
-			$dh = $this->opendir($path);
46
-			if (!is_resource($dh)) {
47
-				return false;
48
-			}
49
-			while (($file = readdir($dh)) !== false) {
50
-				if ($this->is_dir($path . '/' . $file)) {
51
-					$this->rmdir($path . '/' . $file);
52
-				} else {
53
-					$this->unlink($path . '/' . $file);
54
-				}
55
-			}
56
-			$url = $this->constructUrl($path);
57
-			$success = rmdir($url);
58
-			clearstatcache(false, $url);
59
-			return $success;
60
-		} else {
61
-			return false;
62
-		}
63
-	}
64
-
65
-	public function opendir($path) {
66
-		return opendir($this->constructUrl($path));
67
-	}
68
-
69
-	public function filetype($path) {
70
-		return @filetype($this->constructUrl($path));
71
-	}
72
-
73
-	public function file_exists($path) {
74
-		return file_exists($this->constructUrl($path));
75
-	}
76
-
77
-	public function unlink($path) {
78
-		$url = $this->constructUrl($path);
79
-		$success = unlink($url);
80
-		// normally unlink() is supposed to do this implicitly,
81
-		// but doing it anyway just to be sure
82
-		clearstatcache(false, $url);
83
-		return $success;
84
-	}
85
-
86
-	public function fopen($path, $mode) {
87
-		return fopen($this->constructUrl($path), $mode);
88
-	}
89
-
90
-	public function touch($path, $mtime = null) {
91
-		if ($this->file_exists($path)) {
92
-			if (is_null($mtime)) {
93
-				$fh = $this->fopen($path, 'a');
94
-				fwrite($fh, '');
95
-				fclose($fh);
96
-
97
-				return true;
98
-			} else {
99
-				return false; //not supported
100
-			}
101
-		} else {
102
-			$this->file_put_contents($path, '');
103
-			return true;
104
-		}
105
-	}
106
-
107
-	/**
108
-	 * @param string $path
109
-	 * @param string $target
110
-	 */
111
-	public function getFile($path, $target) {
112
-		return copy($this->constructUrl($path), $target);
113
-	}
114
-
115
-	/**
116
-	 * @param string $target
117
-	 */
118
-	public function uploadFile($path, $target) {
119
-		return copy($path, $this->constructUrl($target));
120
-	}
121
-
122
-	public function rename($path1, $path2) {
123
-		return rename($this->constructUrl($path1), $this->constructUrl($path2));
124
-	}
125
-
126
-	public function stat($path) {
127
-		return stat($this->constructUrl($path));
128
-	}
33
+    /**
34
+     * @param string $path
35
+     * @return string|null
36
+     */
37
+    abstract public function constructUrl($path);
38
+
39
+    public function mkdir($path) {
40
+        return mkdir($this->constructUrl($path));
41
+    }
42
+
43
+    public function rmdir($path) {
44
+        if ($this->is_dir($path) && $this->isDeletable($path)) {
45
+            $dh = $this->opendir($path);
46
+            if (!is_resource($dh)) {
47
+                return false;
48
+            }
49
+            while (($file = readdir($dh)) !== false) {
50
+                if ($this->is_dir($path . '/' . $file)) {
51
+                    $this->rmdir($path . '/' . $file);
52
+                } else {
53
+                    $this->unlink($path . '/' . $file);
54
+                }
55
+            }
56
+            $url = $this->constructUrl($path);
57
+            $success = rmdir($url);
58
+            clearstatcache(false, $url);
59
+            return $success;
60
+        } else {
61
+            return false;
62
+        }
63
+    }
64
+
65
+    public function opendir($path) {
66
+        return opendir($this->constructUrl($path));
67
+    }
68
+
69
+    public function filetype($path) {
70
+        return @filetype($this->constructUrl($path));
71
+    }
72
+
73
+    public function file_exists($path) {
74
+        return file_exists($this->constructUrl($path));
75
+    }
76
+
77
+    public function unlink($path) {
78
+        $url = $this->constructUrl($path);
79
+        $success = unlink($url);
80
+        // normally unlink() is supposed to do this implicitly,
81
+        // but doing it anyway just to be sure
82
+        clearstatcache(false, $url);
83
+        return $success;
84
+    }
85
+
86
+    public function fopen($path, $mode) {
87
+        return fopen($this->constructUrl($path), $mode);
88
+    }
89
+
90
+    public function touch($path, $mtime = null) {
91
+        if ($this->file_exists($path)) {
92
+            if (is_null($mtime)) {
93
+                $fh = $this->fopen($path, 'a');
94
+                fwrite($fh, '');
95
+                fclose($fh);
96
+
97
+                return true;
98
+            } else {
99
+                return false; //not supported
100
+            }
101
+        } else {
102
+            $this->file_put_contents($path, '');
103
+            return true;
104
+        }
105
+    }
106
+
107
+    /**
108
+     * @param string $path
109
+     * @param string $target
110
+     */
111
+    public function getFile($path, $target) {
112
+        return copy($this->constructUrl($path), $target);
113
+    }
114
+
115
+    /**
116
+     * @param string $target
117
+     */
118
+    public function uploadFile($path, $target) {
119
+        return copy($path, $this->constructUrl($target));
120
+    }
121
+
122
+    public function rename($path1, $path2) {
123
+        return rename($this->constructUrl($path1), $this->constructUrl($path2));
124
+    }
125
+
126
+    public function stat($path) {
127
+        return stat($this->constructUrl($path));
128
+    }
129 129
 
130 130
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/AmazonS3.php 1 patch
Indentation   +494 added lines, -494 removed lines patch added patch discarded remove patch
@@ -37,7 +37,7 @@  discard block
 block discarded – undo
37 37
 namespace OCA\Files_External\Lib\Storage;
38 38
 
39 39
 set_include_path(get_include_path() . PATH_SEPARATOR .
40
-	\OC_App::getAppPath('files_external') . '/3rdparty/aws-sdk-php');
40
+    \OC_App::getAppPath('files_external') . '/3rdparty/aws-sdk-php');
41 41
 require_once 'aws-autoloader.php';
42 42
 
43 43
 use Aws\S3\S3Client;
@@ -47,498 +47,498 @@  discard block
 block discarded – undo
47 47
 use OC\Files\ObjectStore\S3ConnectionTrait;
48 48
 
49 49
 class AmazonS3 extends \OC\Files\Storage\Common {
50
-	use S3ConnectionTrait;
51
-
52
-	/**
53
-	 * @var array
54
-	 */
55
-	private static $tmpFiles = array();
56
-
57
-	/**
58
-	 * @var int in seconds
59
-	 */
60
-	private $rescanDelay = 10;
61
-
62
-	public function __construct($parameters) {
63
-		parent::__construct($parameters);
64
-		$this->parseParams($parameters);
65
-	}
66
-
67
-	/**
68
-	 * @param string $path
69
-	 * @return string correctly encoded path
70
-	 */
71
-	private function normalizePath($path) {
72
-		$path = trim($path, '/');
73
-
74
-		if (!$path) {
75
-			$path = '.';
76
-		}
77
-
78
-		return $path;
79
-	}
80
-
81
-	private function isRoot($path) {
82
-		return $path === '.';
83
-	}
84
-
85
-	private function cleanKey($path) {
86
-		if ($this->isRoot($path)) {
87
-			return '/';
88
-		}
89
-		return $path;
90
-	}
91
-
92
-	/**
93
-	 * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
94
-	 * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
95
-	 *
96
-	 * @param array $params
97
-	 */
98
-	public function updateLegacyId (array $params) {
99
-		$oldId = 'amazon::' . $params['key'] . md5($params['secret']);
100
-
101
-		// find by old id or bucket
102
-		$stmt = \OC::$server->getDatabaseConnection()->prepare(
103
-			'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
104
-		);
105
-		$stmt->execute(array($oldId, $this->id));
106
-		while ($row = $stmt->fetch()) {
107
-			$storages[$row['id']] = $row['numeric_id'];
108
-		}
109
-
110
-		if (isset($storages[$this->id]) && isset($storages[$oldId])) {
111
-			// if both ids exist, delete the old storage and corresponding filecache entries
112
-			\OC\Files\Cache\Storage::remove($oldId);
113
-		} else if (isset($storages[$oldId])) {
114
-			// if only the old id exists do an update
115
-			$stmt = \OC::$server->getDatabaseConnection()->prepare(
116
-				'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
117
-			);
118
-			$stmt->execute(array($this->id, $oldId));
119
-		}
120
-		// only the bucket based id may exist, do nothing
121
-	}
122
-
123
-	/**
124
-	 * Remove a file or folder
125
-	 *
126
-	 * @param string $path
127
-	 * @return bool
128
-	 */
129
-	protected function remove($path) {
130
-		// remember fileType to reduce http calls
131
-		$fileType = $this->filetype($path);
132
-		if ($fileType === 'dir') {
133
-			return $this->rmdir($path);
134
-		} else if ($fileType === 'file') {
135
-			return $this->unlink($path);
136
-		} else {
137
-			return false;
138
-		}
139
-	}
140
-
141
-	public function mkdir($path) {
142
-		$path = $this->normalizePath($path);
143
-
144
-		if ($this->is_dir($path)) {
145
-			return false;
146
-		}
147
-
148
-		try {
149
-			$this->getConnection()->putObject(array(
150
-				'Bucket' => $this->bucket,
151
-				'Key' => $path . '/',
152
-				'Body' => '',
153
-				'ContentType' => 'httpd/unix-directory'
154
-			));
155
-			$this->testTimeout();
156
-		} catch (S3Exception $e) {
157
-			\OCP\Util::logException('files_external', $e);
158
-			return false;
159
-		}
160
-
161
-		return true;
162
-	}
163
-
164
-	public function file_exists($path) {
165
-		return $this->filetype($path) !== false;
166
-	}
167
-
168
-
169
-	public function rmdir($path) {
170
-		$path = $this->normalizePath($path);
171
-
172
-		if ($this->isRoot($path)) {
173
-			return $this->clearBucket();
174
-		}
175
-
176
-		if (!$this->file_exists($path)) {
177
-			return false;
178
-		}
179
-
180
-		return $this->batchDelete($path);
181
-	}
182
-
183
-	protected function clearBucket() {
184
-		try {
185
-			$this->getConnection()->clearBucket($this->bucket);
186
-			return true;
187
-			// clearBucket() is not working with Ceph, so if it fails we try the slower approach
188
-		} catch (\Exception $e) {
189
-			return $this->batchDelete();
190
-		}
191
-		return false;
192
-	}
193
-
194
-	private function batchDelete ($path = null) {
195
-		$params = array(
196
-			'Bucket' => $this->bucket
197
-		);
198
-		if ($path !== null) {
199
-			$params['Prefix'] = $path . '/';
200
-		}
201
-		try {
202
-			// Since there are no real directories on S3, we need
203
-			// to delete all objects prefixed with the path.
204
-			do {
205
-				// instead of the iterator, manually loop over the list ...
206
-				$objects = $this->getConnection()->listObjects($params);
207
-				// ... so we can delete the files in batches
208
-				$this->getConnection()->deleteObjects(array(
209
-					'Bucket' => $this->bucket,
210
-					'Objects' => $objects['Contents']
211
-				));
212
-				$this->testTimeout();
213
-				// we reached the end when the list is no longer truncated
214
-			} while ($objects['IsTruncated']);
215
-		} catch (S3Exception $e) {
216
-			\OCP\Util::logException('files_external', $e);
217
-			return false;
218
-		}
219
-		return true;
220
-	}
221
-
222
-	public function opendir($path) {
223
-		$path = $this->normalizePath($path);
224
-
225
-		if ($this->isRoot($path)) {
226
-			$path = '';
227
-		} else {
228
-			$path .= '/';
229
-		}
230
-
231
-		try {
232
-			$files = array();
233
-			$result = $this->getConnection()->getIterator('ListObjects', array(
234
-				'Bucket' => $this->bucket,
235
-				'Delimiter' => '/',
236
-				'Prefix' => $path
237
-			), array('return_prefixes' => true));
238
-
239
-			foreach ($result as $object) {
240
-				if (isset($object['Key']) && $object['Key'] === $path) {
241
-					// it's the directory itself, skip
242
-					continue;
243
-				}
244
-				$file = basename(
245
-					isset($object['Key']) ? $object['Key'] : $object['Prefix']
246
-				);
247
-				$files[] = $file;
248
-			}
249
-
250
-			return IteratorDirectory::wrap($files);
251
-		} catch (S3Exception $e) {
252
-			\OCP\Util::logException('files_external', $e);
253
-			return false;
254
-		}
255
-	}
256
-
257
-	public function stat($path) {
258
-		$path = $this->normalizePath($path);
259
-
260
-		try {
261
-			$stat = array();
262
-			if ($this->is_dir($path)) {
263
-				//folders don't really exist
264
-				$stat['size'] = -1; //unknown
265
-				$stat['mtime'] = time() - $this->rescanDelay * 1000;
266
-			} else {
267
-				$result = $this->getConnection()->headObject(array(
268
-					'Bucket' => $this->bucket,
269
-					'Key' => $path
270
-				));
271
-
272
-				$stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
273
-				if ($result['Metadata']['lastmodified']) {
274
-					$stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
275
-				} else {
276
-					$stat['mtime'] = strtotime($result['LastModified']);
277
-				}
278
-			}
279
-			$stat['atime'] = time();
280
-
281
-			return $stat;
282
-		} catch(S3Exception $e) {
283
-			\OCP\Util::logException('files_external', $e);
284
-			return false;
285
-		}
286
-	}
287
-
288
-	public function filetype($path) {
289
-		$path = $this->normalizePath($path);
290
-
291
-		if ($this->isRoot($path)) {
292
-			return 'dir';
293
-		}
294
-
295
-		try {
296
-			if ($this->getConnection()->doesObjectExist($this->bucket, $path)) {
297
-				return 'file';
298
-			}
299
-			if ($this->getConnection()->doesObjectExist($this->bucket, $path.'/')) {
300
-				return 'dir';
301
-			}
302
-		} catch (S3Exception $e) {
303
-			\OCP\Util::logException('files_external', $e);
304
-			return false;
305
-		}
306
-
307
-		return false;
308
-	}
309
-
310
-	public function unlink($path) {
311
-		$path = $this->normalizePath($path);
312
-
313
-		if ($this->is_dir($path)) {
314
-			return $this->rmdir($path);
315
-		}
316
-
317
-		try {
318
-			$this->getConnection()->deleteObject(array(
319
-				'Bucket' => $this->bucket,
320
-				'Key' => $path
321
-			));
322
-			$this->testTimeout();
323
-		} catch (S3Exception $e) {
324
-			\OCP\Util::logException('files_external', $e);
325
-			return false;
326
-		}
327
-
328
-		return true;
329
-	}
330
-
331
-	public function fopen($path, $mode) {
332
-		$path = $this->normalizePath($path);
333
-
334
-		switch ($mode) {
335
-			case 'r':
336
-			case 'rb':
337
-				$tmpFile = \OCP\Files::tmpFile();
338
-				self::$tmpFiles[$tmpFile] = $path;
339
-
340
-				try {
341
-					$this->getConnection()->getObject(array(
342
-						'Bucket' => $this->bucket,
343
-						'Key' => $path,
344
-						'SaveAs' => $tmpFile
345
-					));
346
-				} catch (S3Exception $e) {
347
-					\OCP\Util::logException('files_external', $e);
348
-					return false;
349
-				}
350
-
351
-				return fopen($tmpFile, 'r');
352
-			case 'w':
353
-			case 'wb':
354
-			case 'a':
355
-			case 'ab':
356
-			case 'r+':
357
-			case 'w+':
358
-			case 'wb+':
359
-			case 'a+':
360
-			case 'x':
361
-			case 'x+':
362
-			case 'c':
363
-			case 'c+':
364
-				if (strrpos($path, '.') !== false) {
365
-					$ext = substr($path, strrpos($path, '.'));
366
-				} else {
367
-					$ext = '';
368
-				}
369
-				$tmpFile = \OCP\Files::tmpFile($ext);
370
-				if ($this->file_exists($path)) {
371
-					$source = $this->fopen($path, 'r');
372
-					file_put_contents($tmpFile, $source);
373
-				}
374
-
375
-				$handle = fopen($tmpFile, $mode);
376
-				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
377
-					$this->writeBack($tmpFile, $path);
378
-				});
379
-		}
380
-		return false;
381
-	}
382
-
383
-	public function touch($path, $mtime = null) {
384
-		$path = $this->normalizePath($path);
385
-
386
-		$metadata = array();
387
-		if (is_null($mtime)) {
388
-			$mtime = time();
389
-		}
390
-		$metadata = [
391
-			'lastmodified' => gmdate(\Aws\Common\Enum\DateFormat::RFC1123, $mtime)
392
-		];
393
-
394
-		$fileType = $this->filetype($path);
395
-		try {
396
-			if ($fileType !== false) {
397
-				if ($fileType === 'dir' && ! $this->isRoot($path)) {
398
-					$path .= '/';
399
-				}
400
-				$this->getConnection()->copyObject([
401
-					'Bucket' => $this->bucket,
402
-					'Key' => $this->cleanKey($path),
403
-					'Metadata' => $metadata,
404
-					'CopySource' => $this->bucket . '/' . $path,
405
-					'MetadataDirective' => 'REPLACE',
406
-				]);
407
-				$this->testTimeout();
408
-			} else {
409
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
410
-				$this->getConnection()->putObject([
411
-					'Bucket' => $this->bucket,
412
-					'Key' => $this->cleanKey($path),
413
-					'Metadata' => $metadata,
414
-					'Body' => '',
415
-					'ContentType' => $mimeType,
416
-					'MetadataDirective' => 'REPLACE',
417
-				]);
418
-				$this->testTimeout();
419
-			}
420
-		} catch (S3Exception $e) {
421
-			\OCP\Util::logException('files_external', $e);
422
-			return false;
423
-		}
424
-
425
-		return true;
426
-	}
427
-
428
-	public function copy($path1, $path2) {
429
-		$path1 = $this->normalizePath($path1);
430
-		$path2 = $this->normalizePath($path2);
431
-
432
-		if ($this->is_file($path1)) {
433
-			try {
434
-				$this->getConnection()->copyObject(array(
435
-					'Bucket' => $this->bucket,
436
-					'Key' => $this->cleanKey($path2),
437
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
438
-				));
439
-				$this->testTimeout();
440
-			} catch (S3Exception $e) {
441
-				\OCP\Util::logException('files_external', $e);
442
-				return false;
443
-			}
444
-		} else {
445
-			$this->remove($path2);
446
-
447
-			try {
448
-				$this->getConnection()->copyObject(array(
449
-					'Bucket' => $this->bucket,
450
-					'Key' => $path2 . '/',
451
-					'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
452
-				));
453
-				$this->testTimeout();
454
-			} catch (S3Exception $e) {
455
-				\OCP\Util::logException('files_external', $e);
456
-				return false;
457
-			}
458
-
459
-			$dh = $this->opendir($path1);
460
-			if (is_resource($dh)) {
461
-				while (($file = readdir($dh)) !== false) {
462
-					if (\OC\Files\Filesystem::isIgnoredDir($file)) {
463
-						continue;
464
-					}
465
-
466
-					$source = $path1 . '/' . $file;
467
-					$target = $path2 . '/' . $file;
468
-					$this->copy($source, $target);
469
-				}
470
-			}
471
-		}
472
-
473
-		return true;
474
-	}
475
-
476
-	public function rename($path1, $path2) {
477
-		$path1 = $this->normalizePath($path1);
478
-		$path2 = $this->normalizePath($path2);
479
-
480
-		if ($this->is_file($path1)) {
481
-
482
-			if ($this->copy($path1, $path2) === false) {
483
-				return false;
484
-			}
485
-
486
-			if ($this->unlink($path1) === false) {
487
-				$this->unlink($path2);
488
-				return false;
489
-			}
490
-		} else {
491
-
492
-			if ($this->copy($path1, $path2) === false) {
493
-				return false;
494
-			}
495
-
496
-			if ($this->rmdir($path1) === false) {
497
-				$this->rmdir($path2);
498
-				return false;
499
-			}
500
-		}
501
-
502
-		return true;
503
-	}
504
-
505
-	public function test() {
506
-		$test = $this->getConnection()->getBucketAcl(array(
507
-			'Bucket' => $this->bucket,
508
-		));
509
-		if (isset($test) && !is_null($test->getPath('Owner/ID'))) {
510
-			return true;
511
-		}
512
-		return false;
513
-	}
514
-
515
-	public function getId() {
516
-		return $this->id;
517
-	}
518
-
519
-	public function writeBack($tmpFile, $path) {
520
-		try {
521
-			$this->getConnection()->putObject(array(
522
-				'Bucket' => $this->bucket,
523
-				'Key' => $this->cleanKey($path),
524
-				'SourceFile' => $tmpFile,
525
-				'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile),
526
-				'ContentLength' => filesize($tmpFile)
527
-			));
528
-			$this->testTimeout();
529
-
530
-			unlink($tmpFile);
531
-		} catch (S3Exception $e) {
532
-			\OCP\Util::logException('files_external', $e);
533
-			return false;
534
-		}
535
-	}
536
-
537
-	/**
538
-	 * check if curl is installed
539
-	 */
540
-	public static function checkDependencies() {
541
-		return true;
542
-	}
50
+    use S3ConnectionTrait;
51
+
52
+    /**
53
+     * @var array
54
+     */
55
+    private static $tmpFiles = array();
56
+
57
+    /**
58
+     * @var int in seconds
59
+     */
60
+    private $rescanDelay = 10;
61
+
62
+    public function __construct($parameters) {
63
+        parent::__construct($parameters);
64
+        $this->parseParams($parameters);
65
+    }
66
+
67
+    /**
68
+     * @param string $path
69
+     * @return string correctly encoded path
70
+     */
71
+    private function normalizePath($path) {
72
+        $path = trim($path, '/');
73
+
74
+        if (!$path) {
75
+            $path = '.';
76
+        }
77
+
78
+        return $path;
79
+    }
80
+
81
+    private function isRoot($path) {
82
+        return $path === '.';
83
+    }
84
+
85
+    private function cleanKey($path) {
86
+        if ($this->isRoot($path)) {
87
+            return '/';
88
+        }
89
+        return $path;
90
+    }
91
+
92
+    /**
93
+     * Updates old storage ids (v0.2.1 and older) that are based on key and secret to new ones based on the bucket name.
94
+     * TODO Do this in an update.php. requires iterating over all users and loading the mount.json from their home
95
+     *
96
+     * @param array $params
97
+     */
98
+    public function updateLegacyId (array $params) {
99
+        $oldId = 'amazon::' . $params['key'] . md5($params['secret']);
100
+
101
+        // find by old id or bucket
102
+        $stmt = \OC::$server->getDatabaseConnection()->prepare(
103
+            'SELECT `numeric_id`, `id` FROM `*PREFIX*storages` WHERE `id` IN (?, ?)'
104
+        );
105
+        $stmt->execute(array($oldId, $this->id));
106
+        while ($row = $stmt->fetch()) {
107
+            $storages[$row['id']] = $row['numeric_id'];
108
+        }
109
+
110
+        if (isset($storages[$this->id]) && isset($storages[$oldId])) {
111
+            // if both ids exist, delete the old storage and corresponding filecache entries
112
+            \OC\Files\Cache\Storage::remove($oldId);
113
+        } else if (isset($storages[$oldId])) {
114
+            // if only the old id exists do an update
115
+            $stmt = \OC::$server->getDatabaseConnection()->prepare(
116
+                'UPDATE `*PREFIX*storages` SET `id` = ? WHERE `id` = ?'
117
+            );
118
+            $stmt->execute(array($this->id, $oldId));
119
+        }
120
+        // only the bucket based id may exist, do nothing
121
+    }
122
+
123
+    /**
124
+     * Remove a file or folder
125
+     *
126
+     * @param string $path
127
+     * @return bool
128
+     */
129
+    protected function remove($path) {
130
+        // remember fileType to reduce http calls
131
+        $fileType = $this->filetype($path);
132
+        if ($fileType === 'dir') {
133
+            return $this->rmdir($path);
134
+        } else if ($fileType === 'file') {
135
+            return $this->unlink($path);
136
+        } else {
137
+            return false;
138
+        }
139
+    }
140
+
141
+    public function mkdir($path) {
142
+        $path = $this->normalizePath($path);
143
+
144
+        if ($this->is_dir($path)) {
145
+            return false;
146
+        }
147
+
148
+        try {
149
+            $this->getConnection()->putObject(array(
150
+                'Bucket' => $this->bucket,
151
+                'Key' => $path . '/',
152
+                'Body' => '',
153
+                'ContentType' => 'httpd/unix-directory'
154
+            ));
155
+            $this->testTimeout();
156
+        } catch (S3Exception $e) {
157
+            \OCP\Util::logException('files_external', $e);
158
+            return false;
159
+        }
160
+
161
+        return true;
162
+    }
163
+
164
+    public function file_exists($path) {
165
+        return $this->filetype($path) !== false;
166
+    }
167
+
168
+
169
+    public function rmdir($path) {
170
+        $path = $this->normalizePath($path);
171
+
172
+        if ($this->isRoot($path)) {
173
+            return $this->clearBucket();
174
+        }
175
+
176
+        if (!$this->file_exists($path)) {
177
+            return false;
178
+        }
179
+
180
+        return $this->batchDelete($path);
181
+    }
182
+
183
+    protected function clearBucket() {
184
+        try {
185
+            $this->getConnection()->clearBucket($this->bucket);
186
+            return true;
187
+            // clearBucket() is not working with Ceph, so if it fails we try the slower approach
188
+        } catch (\Exception $e) {
189
+            return $this->batchDelete();
190
+        }
191
+        return false;
192
+    }
193
+
194
+    private function batchDelete ($path = null) {
195
+        $params = array(
196
+            'Bucket' => $this->bucket
197
+        );
198
+        if ($path !== null) {
199
+            $params['Prefix'] = $path . '/';
200
+        }
201
+        try {
202
+            // Since there are no real directories on S3, we need
203
+            // to delete all objects prefixed with the path.
204
+            do {
205
+                // instead of the iterator, manually loop over the list ...
206
+                $objects = $this->getConnection()->listObjects($params);
207
+                // ... so we can delete the files in batches
208
+                $this->getConnection()->deleteObjects(array(
209
+                    'Bucket' => $this->bucket,
210
+                    'Objects' => $objects['Contents']
211
+                ));
212
+                $this->testTimeout();
213
+                // we reached the end when the list is no longer truncated
214
+            } while ($objects['IsTruncated']);
215
+        } catch (S3Exception $e) {
216
+            \OCP\Util::logException('files_external', $e);
217
+            return false;
218
+        }
219
+        return true;
220
+    }
221
+
222
+    public function opendir($path) {
223
+        $path = $this->normalizePath($path);
224
+
225
+        if ($this->isRoot($path)) {
226
+            $path = '';
227
+        } else {
228
+            $path .= '/';
229
+        }
230
+
231
+        try {
232
+            $files = array();
233
+            $result = $this->getConnection()->getIterator('ListObjects', array(
234
+                'Bucket' => $this->bucket,
235
+                'Delimiter' => '/',
236
+                'Prefix' => $path
237
+            ), array('return_prefixes' => true));
238
+
239
+            foreach ($result as $object) {
240
+                if (isset($object['Key']) && $object['Key'] === $path) {
241
+                    // it's the directory itself, skip
242
+                    continue;
243
+                }
244
+                $file = basename(
245
+                    isset($object['Key']) ? $object['Key'] : $object['Prefix']
246
+                );
247
+                $files[] = $file;
248
+            }
249
+
250
+            return IteratorDirectory::wrap($files);
251
+        } catch (S3Exception $e) {
252
+            \OCP\Util::logException('files_external', $e);
253
+            return false;
254
+        }
255
+    }
256
+
257
+    public function stat($path) {
258
+        $path = $this->normalizePath($path);
259
+
260
+        try {
261
+            $stat = array();
262
+            if ($this->is_dir($path)) {
263
+                //folders don't really exist
264
+                $stat['size'] = -1; //unknown
265
+                $stat['mtime'] = time() - $this->rescanDelay * 1000;
266
+            } else {
267
+                $result = $this->getConnection()->headObject(array(
268
+                    'Bucket' => $this->bucket,
269
+                    'Key' => $path
270
+                ));
271
+
272
+                $stat['size'] = $result['ContentLength'] ? $result['ContentLength'] : 0;
273
+                if ($result['Metadata']['lastmodified']) {
274
+                    $stat['mtime'] = strtotime($result['Metadata']['lastmodified']);
275
+                } else {
276
+                    $stat['mtime'] = strtotime($result['LastModified']);
277
+                }
278
+            }
279
+            $stat['atime'] = time();
280
+
281
+            return $stat;
282
+        } catch(S3Exception $e) {
283
+            \OCP\Util::logException('files_external', $e);
284
+            return false;
285
+        }
286
+    }
287
+
288
+    public function filetype($path) {
289
+        $path = $this->normalizePath($path);
290
+
291
+        if ($this->isRoot($path)) {
292
+            return 'dir';
293
+        }
294
+
295
+        try {
296
+            if ($this->getConnection()->doesObjectExist($this->bucket, $path)) {
297
+                return 'file';
298
+            }
299
+            if ($this->getConnection()->doesObjectExist($this->bucket, $path.'/')) {
300
+                return 'dir';
301
+            }
302
+        } catch (S3Exception $e) {
303
+            \OCP\Util::logException('files_external', $e);
304
+            return false;
305
+        }
306
+
307
+        return false;
308
+    }
309
+
310
+    public function unlink($path) {
311
+        $path = $this->normalizePath($path);
312
+
313
+        if ($this->is_dir($path)) {
314
+            return $this->rmdir($path);
315
+        }
316
+
317
+        try {
318
+            $this->getConnection()->deleteObject(array(
319
+                'Bucket' => $this->bucket,
320
+                'Key' => $path
321
+            ));
322
+            $this->testTimeout();
323
+        } catch (S3Exception $e) {
324
+            \OCP\Util::logException('files_external', $e);
325
+            return false;
326
+        }
327
+
328
+        return true;
329
+    }
330
+
331
+    public function fopen($path, $mode) {
332
+        $path = $this->normalizePath($path);
333
+
334
+        switch ($mode) {
335
+            case 'r':
336
+            case 'rb':
337
+                $tmpFile = \OCP\Files::tmpFile();
338
+                self::$tmpFiles[$tmpFile] = $path;
339
+
340
+                try {
341
+                    $this->getConnection()->getObject(array(
342
+                        'Bucket' => $this->bucket,
343
+                        'Key' => $path,
344
+                        'SaveAs' => $tmpFile
345
+                    ));
346
+                } catch (S3Exception $e) {
347
+                    \OCP\Util::logException('files_external', $e);
348
+                    return false;
349
+                }
350
+
351
+                return fopen($tmpFile, 'r');
352
+            case 'w':
353
+            case 'wb':
354
+            case 'a':
355
+            case 'ab':
356
+            case 'r+':
357
+            case 'w+':
358
+            case 'wb+':
359
+            case 'a+':
360
+            case 'x':
361
+            case 'x+':
362
+            case 'c':
363
+            case 'c+':
364
+                if (strrpos($path, '.') !== false) {
365
+                    $ext = substr($path, strrpos($path, '.'));
366
+                } else {
367
+                    $ext = '';
368
+                }
369
+                $tmpFile = \OCP\Files::tmpFile($ext);
370
+                if ($this->file_exists($path)) {
371
+                    $source = $this->fopen($path, 'r');
372
+                    file_put_contents($tmpFile, $source);
373
+                }
374
+
375
+                $handle = fopen($tmpFile, $mode);
376
+                return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
377
+                    $this->writeBack($tmpFile, $path);
378
+                });
379
+        }
380
+        return false;
381
+    }
382
+
383
+    public function touch($path, $mtime = null) {
384
+        $path = $this->normalizePath($path);
385
+
386
+        $metadata = array();
387
+        if (is_null($mtime)) {
388
+            $mtime = time();
389
+        }
390
+        $metadata = [
391
+            'lastmodified' => gmdate(\Aws\Common\Enum\DateFormat::RFC1123, $mtime)
392
+        ];
393
+
394
+        $fileType = $this->filetype($path);
395
+        try {
396
+            if ($fileType !== false) {
397
+                if ($fileType === 'dir' && ! $this->isRoot($path)) {
398
+                    $path .= '/';
399
+                }
400
+                $this->getConnection()->copyObject([
401
+                    'Bucket' => $this->bucket,
402
+                    'Key' => $this->cleanKey($path),
403
+                    'Metadata' => $metadata,
404
+                    'CopySource' => $this->bucket . '/' . $path,
405
+                    'MetadataDirective' => 'REPLACE',
406
+                ]);
407
+                $this->testTimeout();
408
+            } else {
409
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
410
+                $this->getConnection()->putObject([
411
+                    'Bucket' => $this->bucket,
412
+                    'Key' => $this->cleanKey($path),
413
+                    'Metadata' => $metadata,
414
+                    'Body' => '',
415
+                    'ContentType' => $mimeType,
416
+                    'MetadataDirective' => 'REPLACE',
417
+                ]);
418
+                $this->testTimeout();
419
+            }
420
+        } catch (S3Exception $e) {
421
+            \OCP\Util::logException('files_external', $e);
422
+            return false;
423
+        }
424
+
425
+        return true;
426
+    }
427
+
428
+    public function copy($path1, $path2) {
429
+        $path1 = $this->normalizePath($path1);
430
+        $path2 = $this->normalizePath($path2);
431
+
432
+        if ($this->is_file($path1)) {
433
+            try {
434
+                $this->getConnection()->copyObject(array(
435
+                    'Bucket' => $this->bucket,
436
+                    'Key' => $this->cleanKey($path2),
437
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1)
438
+                ));
439
+                $this->testTimeout();
440
+            } catch (S3Exception $e) {
441
+                \OCP\Util::logException('files_external', $e);
442
+                return false;
443
+            }
444
+        } else {
445
+            $this->remove($path2);
446
+
447
+            try {
448
+                $this->getConnection()->copyObject(array(
449
+                    'Bucket' => $this->bucket,
450
+                    'Key' => $path2 . '/',
451
+                    'CopySource' => S3Client::encodeKey($this->bucket . '/' . $path1 . '/')
452
+                ));
453
+                $this->testTimeout();
454
+            } catch (S3Exception $e) {
455
+                \OCP\Util::logException('files_external', $e);
456
+                return false;
457
+            }
458
+
459
+            $dh = $this->opendir($path1);
460
+            if (is_resource($dh)) {
461
+                while (($file = readdir($dh)) !== false) {
462
+                    if (\OC\Files\Filesystem::isIgnoredDir($file)) {
463
+                        continue;
464
+                    }
465
+
466
+                    $source = $path1 . '/' . $file;
467
+                    $target = $path2 . '/' . $file;
468
+                    $this->copy($source, $target);
469
+                }
470
+            }
471
+        }
472
+
473
+        return true;
474
+    }
475
+
476
+    public function rename($path1, $path2) {
477
+        $path1 = $this->normalizePath($path1);
478
+        $path2 = $this->normalizePath($path2);
479
+
480
+        if ($this->is_file($path1)) {
481
+
482
+            if ($this->copy($path1, $path2) === false) {
483
+                return false;
484
+            }
485
+
486
+            if ($this->unlink($path1) === false) {
487
+                $this->unlink($path2);
488
+                return false;
489
+            }
490
+        } else {
491
+
492
+            if ($this->copy($path1, $path2) === false) {
493
+                return false;
494
+            }
495
+
496
+            if ($this->rmdir($path1) === false) {
497
+                $this->rmdir($path2);
498
+                return false;
499
+            }
500
+        }
501
+
502
+        return true;
503
+    }
504
+
505
+    public function test() {
506
+        $test = $this->getConnection()->getBucketAcl(array(
507
+            'Bucket' => $this->bucket,
508
+        ));
509
+        if (isset($test) && !is_null($test->getPath('Owner/ID'))) {
510
+            return true;
511
+        }
512
+        return false;
513
+    }
514
+
515
+    public function getId() {
516
+        return $this->id;
517
+    }
518
+
519
+    public function writeBack($tmpFile, $path) {
520
+        try {
521
+            $this->getConnection()->putObject(array(
522
+                'Bucket' => $this->bucket,
523
+                'Key' => $this->cleanKey($path),
524
+                'SourceFile' => $tmpFile,
525
+                'ContentType' => \OC::$server->getMimeTypeDetector()->detect($tmpFile),
526
+                'ContentLength' => filesize($tmpFile)
527
+            ));
528
+            $this->testTimeout();
529
+
530
+            unlink($tmpFile);
531
+        } catch (S3Exception $e) {
532
+            \OCP\Util::logException('files_external', $e);
533
+            return false;
534
+        }
535
+    }
536
+
537
+    /**
538
+     * check if curl is installed
539
+     */
540
+    public static function checkDependencies() {
541
+        return true;
542
+    }
543 543
 
544 544
 }
Please login to merge, or discard this patch.
apps/files_external/lib/Lib/Storage/SMB.php 1 patch
Indentation   +450 added lines, -450 removed lines patch added patch discarded remove patch
@@ -53,454 +53,454 @@
 block discarded – undo
53 53
 use OCP\Files\StorageNotAvailableException;
54 54
 
55 55
 class SMB extends Common implements INotifyStorage {
56
-	/**
57
-	 * @var \Icewind\SMB\Server
58
-	 */
59
-	protected $server;
60
-
61
-	/**
62
-	 * @var \Icewind\SMB\Share
63
-	 */
64
-	protected $share;
65
-
66
-	/**
67
-	 * @var string
68
-	 */
69
-	protected $root;
70
-
71
-	/**
72
-	 * @var \Icewind\SMB\FileInfo[]
73
-	 */
74
-	protected $statCache;
75
-
76
-	public function __construct($params) {
77
-		if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
78
-			if (Server::NativeAvailable()) {
79
-				$this->server = new NativeServer($params['host'], $params['user'], $params['password']);
80
-			} else {
81
-				$this->server = new Server($params['host'], $params['user'], $params['password']);
82
-			}
83
-			$this->share = $this->server->getShare(trim($params['share'], '/'));
84
-
85
-			$this->root = isset($params['root']) ? $params['root'] : '/';
86
-			if (!$this->root || $this->root[0] != '/') {
87
-				$this->root = '/' . $this->root;
88
-			}
89
-			if (substr($this->root, -1, 1) != '/') {
90
-				$this->root .= '/';
91
-			}
92
-		} else {
93
-			throw new \Exception('Invalid configuration');
94
-		}
95
-		$this->statCache = new CappedMemoryCache();
96
-	}
97
-
98
-	/**
99
-	 * @return string
100
-	 */
101
-	public function getId() {
102
-		// FIXME: double slash to keep compatible with the old storage ids,
103
-		// failure to do so will lead to creation of a new storage id and
104
-		// loss of shares from the storage
105
-		return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
106
-	}
107
-
108
-	/**
109
-	 * @param string $path
110
-	 * @return string
111
-	 */
112
-	protected function buildPath($path) {
113
-		return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
114
-	}
115
-
116
-	protected function relativePath($fullPath) {
117
-		if ($fullPath === $this->root) {
118
-			return '';
119
-		} else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
120
-			return substr($fullPath, strlen($this->root));
121
-		} else {
122
-			return null;
123
-		}
124
-	}
125
-
126
-	/**
127
-	 * @param string $path
128
-	 * @return \Icewind\SMB\IFileInfo
129
-	 * @throws StorageNotAvailableException
130
-	 */
131
-	protected function getFileInfo($path) {
132
-		try {
133
-			$path = $this->buildPath($path);
134
-			if (!isset($this->statCache[$path])) {
135
-				$this->statCache[$path] = $this->share->stat($path);
136
-			}
137
-			return $this->statCache[$path];
138
-		} catch (ConnectException $e) {
139
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * @param string $path
145
-	 * @return \Icewind\SMB\IFileInfo[]
146
-	 * @throws StorageNotAvailableException
147
-	 */
148
-	protected function getFolderContents($path) {
149
-		try {
150
-			$path = $this->buildPath($path);
151
-			$files = $this->share->dir($path);
152
-			foreach ($files as $file) {
153
-				$this->statCache[$path . '/' . $file->getName()] = $file;
154
-			}
155
-			return array_filter($files, function (IFileInfo $file) {
156
-				return !$file->isHidden();
157
-			});
158
-		} catch (ConnectException $e) {
159
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
160
-		}
161
-	}
162
-
163
-	/**
164
-	 * @param \Icewind\SMB\IFileInfo $info
165
-	 * @return array
166
-	 */
167
-	protected function formatInfo($info) {
168
-		return array(
169
-			'size' => $info->getSize(),
170
-			'mtime' => $info->getMTime()
171
-		);
172
-	}
173
-
174
-	/**
175
-	 * @param string $path
176
-	 * @return array
177
-	 */
178
-	public function stat($path) {
179
-		$result = $this->formatInfo($this->getFileInfo($path));
180
-		if ($this->remoteIsShare() && $this->isRootDir($path)) {
181
-			$result['mtime'] = $this->shareMTime();
182
-		}
183
-		return $result;
184
-	}
185
-
186
-	/**
187
-	 * get the best guess for the modification time of the share
188
-	 *
189
-	 * @return int
190
-	 */
191
-	private function shareMTime() {
192
-		$highestMTime = 0;
193
-		$files = $this->share->dir($this->root);
194
-		foreach ($files as $fileInfo) {
195
-			if ($fileInfo->getMTime() > $highestMTime) {
196
-				$highestMTime = $fileInfo->getMTime();
197
-			}
198
-		}
199
-		return $highestMTime;
200
-	}
201
-
202
-	/**
203
-	 * Check if the path is our root dir (not the smb one)
204
-	 *
205
-	 * @param string $path the path
206
-	 * @return bool
207
-	 */
208
-	private function isRootDir($path) {
209
-		return $path === '' || $path === '/' || $path === '.';
210
-	}
211
-
212
-	/**
213
-	 * Check if our root points to a smb share
214
-	 *
215
-	 * @return bool true if our root points to a share false otherwise
216
-	 */
217
-	private function remoteIsShare() {
218
-		return $this->share->getName() && (!$this->root || $this->root === '/');
219
-	}
220
-
221
-	/**
222
-	 * @param string $path
223
-	 * @return bool
224
-	 */
225
-	public function unlink($path) {
226
-		try {
227
-			if ($this->is_dir($path)) {
228
-				return $this->rmdir($path);
229
-			} else {
230
-				$path = $this->buildPath($path);
231
-				unset($this->statCache[$path]);
232
-				$this->share->del($path);
233
-				return true;
234
-			}
235
-		} catch (NotFoundException $e) {
236
-			return false;
237
-		} catch (ForbiddenException $e) {
238
-			return false;
239
-		} catch (ConnectException $e) {
240
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
241
-		}
242
-	}
243
-
244
-	/**
245
-	 * @param string $path1 the old name
246
-	 * @param string $path2 the new name
247
-	 * @return bool
248
-	 */
249
-	public function rename($path1, $path2) {
250
-		try {
251
-			$this->remove($path2);
252
-			$path1 = $this->buildPath($path1);
253
-			$path2 = $this->buildPath($path2);
254
-			return $this->share->rename($path1, $path2);
255
-		} catch (NotFoundException $e) {
256
-			return false;
257
-		} catch (ForbiddenException $e) {
258
-			return false;
259
-		} catch (ConnectException $e) {
260
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
261
-		}
262
-	}
263
-
264
-	/**
265
-	 * check if a file or folder has been updated since $time
266
-	 *
267
-	 * @param string $path
268
-	 * @param int $time
269
-	 * @return bool
270
-	 */
271
-	public function hasUpdated($path, $time) {
272
-		if (!$path and $this->root == '/') {
273
-			// mtime doesn't work for shares, but giving the nature of the backend,
274
-			// doing a full update is still just fast enough
275
-			return true;
276
-		} else {
277
-			$actualTime = $this->filemtime($path);
278
-			return $actualTime > $time;
279
-		}
280
-	}
281
-
282
-	/**
283
-	 * @param string $path
284
-	 * @param string $mode
285
-	 * @return resource|false
286
-	 */
287
-	public function fopen($path, $mode) {
288
-		$fullPath = $this->buildPath($path);
289
-		try {
290
-			switch ($mode) {
291
-				case 'r':
292
-				case 'rb':
293
-					if (!$this->file_exists($path)) {
294
-						return false;
295
-					}
296
-					return $this->share->read($fullPath);
297
-				case 'w':
298
-				case 'wb':
299
-					$source = $this->share->write($fullPath);
300
-					return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
301
-						unset($this->statCache[$fullPath]);
302
-					});
303
-				case 'a':
304
-				case 'ab':
305
-				case 'r+':
306
-				case 'w+':
307
-				case 'wb+':
308
-				case 'a+':
309
-				case 'x':
310
-				case 'x+':
311
-				case 'c':
312
-				case 'c+':
313
-					//emulate these
314
-					if (strrpos($path, '.') !== false) {
315
-						$ext = substr($path, strrpos($path, '.'));
316
-					} else {
317
-						$ext = '';
318
-					}
319
-					if ($this->file_exists($path)) {
320
-						if (!$this->isUpdatable($path)) {
321
-							return false;
322
-						}
323
-						$tmpFile = $this->getCachedFile($path);
324
-					} else {
325
-						if (!$this->isCreatable(dirname($path))) {
326
-							return false;
327
-						}
328
-						$tmpFile = \OCP\Files::tmpFile($ext);
329
-					}
330
-					$source = fopen($tmpFile, $mode);
331
-					$share = $this->share;
332
-					return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
333
-						unset($this->statCache[$fullPath]);
334
-						$share->put($tmpFile, $fullPath);
335
-						unlink($tmpFile);
336
-					});
337
-			}
338
-			return false;
339
-		} catch (NotFoundException $e) {
340
-			return false;
341
-		} catch (ForbiddenException $e) {
342
-			return false;
343
-		} catch (ConnectException $e) {
344
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
345
-		}
346
-	}
347
-
348
-	public function rmdir($path) {
349
-		try {
350
-			$this->statCache = array();
351
-			$content = $this->share->dir($this->buildPath($path));
352
-			foreach ($content as $file) {
353
-				if ($file->isDirectory()) {
354
-					$this->rmdir($path . '/' . $file->getName());
355
-				} else {
356
-					$this->share->del($file->getPath());
357
-				}
358
-			}
359
-			$this->share->rmdir($this->buildPath($path));
360
-			return true;
361
-		} catch (NotFoundException $e) {
362
-			return false;
363
-		} catch (ForbiddenException $e) {
364
-			return false;
365
-		} catch (ConnectException $e) {
366
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
367
-		}
368
-	}
369
-
370
-	public function touch($path, $time = null) {
371
-		try {
372
-			if (!$this->file_exists($path)) {
373
-				$fh = $this->share->write($this->buildPath($path));
374
-				fclose($fh);
375
-				return true;
376
-			}
377
-			return false;
378
-		} catch (ConnectException $e) {
379
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
380
-		}
381
-	}
382
-
383
-	public function opendir($path) {
384
-		try {
385
-			$files = $this->getFolderContents($path);
386
-		} catch (NotFoundException $e) {
387
-			return false;
388
-		} catch (ForbiddenException $e) {
389
-			return false;
390
-		}
391
-		$names = array_map(function ($info) {
392
-			/** @var \Icewind\SMB\IFileInfo $info */
393
-			return $info->getName();
394
-		}, $files);
395
-		return IteratorDirectory::wrap($names);
396
-	}
397
-
398
-	public function filetype($path) {
399
-		try {
400
-			return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
401
-		} catch (NotFoundException $e) {
402
-			return false;
403
-		} catch (ForbiddenException $e) {
404
-			return false;
405
-		}
406
-	}
407
-
408
-	public function mkdir($path) {
409
-		$path = $this->buildPath($path);
410
-		try {
411
-			$this->share->mkdir($path);
412
-			return true;
413
-		} catch (ConnectException $e) {
414
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
415
-		} catch (Exception $e) {
416
-			return false;
417
-		}
418
-	}
419
-
420
-	public function file_exists($path) {
421
-		try {
422
-			$this->getFileInfo($path);
423
-			return true;
424
-		} catch (NotFoundException $e) {
425
-			return false;
426
-		} catch (ForbiddenException $e) {
427
-			return false;
428
-		} catch (ConnectException $e) {
429
-			throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
430
-		}
431
-	}
432
-
433
-	public function isReadable($path) {
434
-		try {
435
-			$info = $this->getFileInfo($path);
436
-			return !$info->isHidden();
437
-		} catch (NotFoundException $e) {
438
-			return false;
439
-		} catch (ForbiddenException $e) {
440
-			return false;
441
-		}
442
-	}
443
-
444
-	public function isUpdatable($path) {
445
-		try {
446
-			$info = $this->getFileInfo($path);
447
-			// following windows behaviour for read-only folders: they can be written into
448
-			// (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
449
-			return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
450
-		} catch (NotFoundException $e) {
451
-			return false;
452
-		} catch (ForbiddenException $e) {
453
-			return false;
454
-		}
455
-	}
456
-
457
-	public function isDeletable($path) {
458
-		try {
459
-			$info = $this->getFileInfo($path);
460
-			return !$info->isHidden() && !$info->isReadOnly();
461
-		} catch (NotFoundException $e) {
462
-			return false;
463
-		} catch (ForbiddenException $e) {
464
-			return false;
465
-		}
466
-	}
467
-
468
-	/**
469
-	 * check if smbclient is installed
470
-	 */
471
-	public static function checkDependencies() {
472
-		return (
473
-			(bool)\OC_Helper::findBinaryPath('smbclient')
474
-			|| Server::NativeAvailable()
475
-		) ? true : ['smbclient'];
476
-	}
477
-
478
-	/**
479
-	 * Test a storage for availability
480
-	 *
481
-	 * @return bool
482
-	 */
483
-	public function test() {
484
-		try {
485
-			return parent::test();
486
-		} catch (Exception $e) {
487
-			return false;
488
-		}
489
-	}
490
-
491
-	public function listen($path, callable $callback) {
492
-		$this->notify($path)->listen(function (IChange $change) use ($callback) {
493
-			if ($change instanceof IRenameChange) {
494
-				return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
495
-			} else {
496
-				return $callback($change->getType(), $change->getPath());
497
-			}
498
-		});
499
-	}
500
-
501
-	public function notify($path) {
502
-		$path = '/' . ltrim($path, '/');
503
-		$shareNotifyHandler = $this->share->notify($this->buildPath($path));
504
-		return new SMBNotifyHandler($shareNotifyHandler, $this->root);
505
-	}
56
+    /**
57
+     * @var \Icewind\SMB\Server
58
+     */
59
+    protected $server;
60
+
61
+    /**
62
+     * @var \Icewind\SMB\Share
63
+     */
64
+    protected $share;
65
+
66
+    /**
67
+     * @var string
68
+     */
69
+    protected $root;
70
+
71
+    /**
72
+     * @var \Icewind\SMB\FileInfo[]
73
+     */
74
+    protected $statCache;
75
+
76
+    public function __construct($params) {
77
+        if (isset($params['host']) && isset($params['user']) && isset($params['password']) && isset($params['share'])) {
78
+            if (Server::NativeAvailable()) {
79
+                $this->server = new NativeServer($params['host'], $params['user'], $params['password']);
80
+            } else {
81
+                $this->server = new Server($params['host'], $params['user'], $params['password']);
82
+            }
83
+            $this->share = $this->server->getShare(trim($params['share'], '/'));
84
+
85
+            $this->root = isset($params['root']) ? $params['root'] : '/';
86
+            if (!$this->root || $this->root[0] != '/') {
87
+                $this->root = '/' . $this->root;
88
+            }
89
+            if (substr($this->root, -1, 1) != '/') {
90
+                $this->root .= '/';
91
+            }
92
+        } else {
93
+            throw new \Exception('Invalid configuration');
94
+        }
95
+        $this->statCache = new CappedMemoryCache();
96
+    }
97
+
98
+    /**
99
+     * @return string
100
+     */
101
+    public function getId() {
102
+        // FIXME: double slash to keep compatible with the old storage ids,
103
+        // failure to do so will lead to creation of a new storage id and
104
+        // loss of shares from the storage
105
+        return 'smb::' . $this->server->getUser() . '@' . $this->server->getHost() . '//' . $this->share->getName() . '/' . $this->root;
106
+    }
107
+
108
+    /**
109
+     * @param string $path
110
+     * @return string
111
+     */
112
+    protected function buildPath($path) {
113
+        return Filesystem::normalizePath($this->root . '/' . $path, true, false, true);
114
+    }
115
+
116
+    protected function relativePath($fullPath) {
117
+        if ($fullPath === $this->root) {
118
+            return '';
119
+        } else if (substr($fullPath, 0, strlen($this->root)) === $this->root) {
120
+            return substr($fullPath, strlen($this->root));
121
+        } else {
122
+            return null;
123
+        }
124
+    }
125
+
126
+    /**
127
+     * @param string $path
128
+     * @return \Icewind\SMB\IFileInfo
129
+     * @throws StorageNotAvailableException
130
+     */
131
+    protected function getFileInfo($path) {
132
+        try {
133
+            $path = $this->buildPath($path);
134
+            if (!isset($this->statCache[$path])) {
135
+                $this->statCache[$path] = $this->share->stat($path);
136
+            }
137
+            return $this->statCache[$path];
138
+        } catch (ConnectException $e) {
139
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
140
+        }
141
+    }
142
+
143
+    /**
144
+     * @param string $path
145
+     * @return \Icewind\SMB\IFileInfo[]
146
+     * @throws StorageNotAvailableException
147
+     */
148
+    protected function getFolderContents($path) {
149
+        try {
150
+            $path = $this->buildPath($path);
151
+            $files = $this->share->dir($path);
152
+            foreach ($files as $file) {
153
+                $this->statCache[$path . '/' . $file->getName()] = $file;
154
+            }
155
+            return array_filter($files, function (IFileInfo $file) {
156
+                return !$file->isHidden();
157
+            });
158
+        } catch (ConnectException $e) {
159
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
160
+        }
161
+    }
162
+
163
+    /**
164
+     * @param \Icewind\SMB\IFileInfo $info
165
+     * @return array
166
+     */
167
+    protected function formatInfo($info) {
168
+        return array(
169
+            'size' => $info->getSize(),
170
+            'mtime' => $info->getMTime()
171
+        );
172
+    }
173
+
174
+    /**
175
+     * @param string $path
176
+     * @return array
177
+     */
178
+    public function stat($path) {
179
+        $result = $this->formatInfo($this->getFileInfo($path));
180
+        if ($this->remoteIsShare() && $this->isRootDir($path)) {
181
+            $result['mtime'] = $this->shareMTime();
182
+        }
183
+        return $result;
184
+    }
185
+
186
+    /**
187
+     * get the best guess for the modification time of the share
188
+     *
189
+     * @return int
190
+     */
191
+    private function shareMTime() {
192
+        $highestMTime = 0;
193
+        $files = $this->share->dir($this->root);
194
+        foreach ($files as $fileInfo) {
195
+            if ($fileInfo->getMTime() > $highestMTime) {
196
+                $highestMTime = $fileInfo->getMTime();
197
+            }
198
+        }
199
+        return $highestMTime;
200
+    }
201
+
202
+    /**
203
+     * Check if the path is our root dir (not the smb one)
204
+     *
205
+     * @param string $path the path
206
+     * @return bool
207
+     */
208
+    private function isRootDir($path) {
209
+        return $path === '' || $path === '/' || $path === '.';
210
+    }
211
+
212
+    /**
213
+     * Check if our root points to a smb share
214
+     *
215
+     * @return bool true if our root points to a share false otherwise
216
+     */
217
+    private function remoteIsShare() {
218
+        return $this->share->getName() && (!$this->root || $this->root === '/');
219
+    }
220
+
221
+    /**
222
+     * @param string $path
223
+     * @return bool
224
+     */
225
+    public function unlink($path) {
226
+        try {
227
+            if ($this->is_dir($path)) {
228
+                return $this->rmdir($path);
229
+            } else {
230
+                $path = $this->buildPath($path);
231
+                unset($this->statCache[$path]);
232
+                $this->share->del($path);
233
+                return true;
234
+            }
235
+        } catch (NotFoundException $e) {
236
+            return false;
237
+        } catch (ForbiddenException $e) {
238
+            return false;
239
+        } catch (ConnectException $e) {
240
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
241
+        }
242
+    }
243
+
244
+    /**
245
+     * @param string $path1 the old name
246
+     * @param string $path2 the new name
247
+     * @return bool
248
+     */
249
+    public function rename($path1, $path2) {
250
+        try {
251
+            $this->remove($path2);
252
+            $path1 = $this->buildPath($path1);
253
+            $path2 = $this->buildPath($path2);
254
+            return $this->share->rename($path1, $path2);
255
+        } catch (NotFoundException $e) {
256
+            return false;
257
+        } catch (ForbiddenException $e) {
258
+            return false;
259
+        } catch (ConnectException $e) {
260
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
261
+        }
262
+    }
263
+
264
+    /**
265
+     * check if a file or folder has been updated since $time
266
+     *
267
+     * @param string $path
268
+     * @param int $time
269
+     * @return bool
270
+     */
271
+    public function hasUpdated($path, $time) {
272
+        if (!$path and $this->root == '/') {
273
+            // mtime doesn't work for shares, but giving the nature of the backend,
274
+            // doing a full update is still just fast enough
275
+            return true;
276
+        } else {
277
+            $actualTime = $this->filemtime($path);
278
+            return $actualTime > $time;
279
+        }
280
+    }
281
+
282
+    /**
283
+     * @param string $path
284
+     * @param string $mode
285
+     * @return resource|false
286
+     */
287
+    public function fopen($path, $mode) {
288
+        $fullPath = $this->buildPath($path);
289
+        try {
290
+            switch ($mode) {
291
+                case 'r':
292
+                case 'rb':
293
+                    if (!$this->file_exists($path)) {
294
+                        return false;
295
+                    }
296
+                    return $this->share->read($fullPath);
297
+                case 'w':
298
+                case 'wb':
299
+                    $source = $this->share->write($fullPath);
300
+                    return CallBackWrapper::wrap($source, null, null, function () use ($fullPath) {
301
+                        unset($this->statCache[$fullPath]);
302
+                    });
303
+                case 'a':
304
+                case 'ab':
305
+                case 'r+':
306
+                case 'w+':
307
+                case 'wb+':
308
+                case 'a+':
309
+                case 'x':
310
+                case 'x+':
311
+                case 'c':
312
+                case 'c+':
313
+                    //emulate these
314
+                    if (strrpos($path, '.') !== false) {
315
+                        $ext = substr($path, strrpos($path, '.'));
316
+                    } else {
317
+                        $ext = '';
318
+                    }
319
+                    if ($this->file_exists($path)) {
320
+                        if (!$this->isUpdatable($path)) {
321
+                            return false;
322
+                        }
323
+                        $tmpFile = $this->getCachedFile($path);
324
+                    } else {
325
+                        if (!$this->isCreatable(dirname($path))) {
326
+                            return false;
327
+                        }
328
+                        $tmpFile = \OCP\Files::tmpFile($ext);
329
+                    }
330
+                    $source = fopen($tmpFile, $mode);
331
+                    $share = $this->share;
332
+                    return CallbackWrapper::wrap($source, null, null, function () use ($tmpFile, $fullPath, $share) {
333
+                        unset($this->statCache[$fullPath]);
334
+                        $share->put($tmpFile, $fullPath);
335
+                        unlink($tmpFile);
336
+                    });
337
+            }
338
+            return false;
339
+        } catch (NotFoundException $e) {
340
+            return false;
341
+        } catch (ForbiddenException $e) {
342
+            return false;
343
+        } catch (ConnectException $e) {
344
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
345
+        }
346
+    }
347
+
348
+    public function rmdir($path) {
349
+        try {
350
+            $this->statCache = array();
351
+            $content = $this->share->dir($this->buildPath($path));
352
+            foreach ($content as $file) {
353
+                if ($file->isDirectory()) {
354
+                    $this->rmdir($path . '/' . $file->getName());
355
+                } else {
356
+                    $this->share->del($file->getPath());
357
+                }
358
+            }
359
+            $this->share->rmdir($this->buildPath($path));
360
+            return true;
361
+        } catch (NotFoundException $e) {
362
+            return false;
363
+        } catch (ForbiddenException $e) {
364
+            return false;
365
+        } catch (ConnectException $e) {
366
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
367
+        }
368
+    }
369
+
370
+    public function touch($path, $time = null) {
371
+        try {
372
+            if (!$this->file_exists($path)) {
373
+                $fh = $this->share->write($this->buildPath($path));
374
+                fclose($fh);
375
+                return true;
376
+            }
377
+            return false;
378
+        } catch (ConnectException $e) {
379
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
380
+        }
381
+    }
382
+
383
+    public function opendir($path) {
384
+        try {
385
+            $files = $this->getFolderContents($path);
386
+        } catch (NotFoundException $e) {
387
+            return false;
388
+        } catch (ForbiddenException $e) {
389
+            return false;
390
+        }
391
+        $names = array_map(function ($info) {
392
+            /** @var \Icewind\SMB\IFileInfo $info */
393
+            return $info->getName();
394
+        }, $files);
395
+        return IteratorDirectory::wrap($names);
396
+    }
397
+
398
+    public function filetype($path) {
399
+        try {
400
+            return $this->getFileInfo($path)->isDirectory() ? 'dir' : 'file';
401
+        } catch (NotFoundException $e) {
402
+            return false;
403
+        } catch (ForbiddenException $e) {
404
+            return false;
405
+        }
406
+    }
407
+
408
+    public function mkdir($path) {
409
+        $path = $this->buildPath($path);
410
+        try {
411
+            $this->share->mkdir($path);
412
+            return true;
413
+        } catch (ConnectException $e) {
414
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
415
+        } catch (Exception $e) {
416
+            return false;
417
+        }
418
+    }
419
+
420
+    public function file_exists($path) {
421
+        try {
422
+            $this->getFileInfo($path);
423
+            return true;
424
+        } catch (NotFoundException $e) {
425
+            return false;
426
+        } catch (ForbiddenException $e) {
427
+            return false;
428
+        } catch (ConnectException $e) {
429
+            throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e);
430
+        }
431
+    }
432
+
433
+    public function isReadable($path) {
434
+        try {
435
+            $info = $this->getFileInfo($path);
436
+            return !$info->isHidden();
437
+        } catch (NotFoundException $e) {
438
+            return false;
439
+        } catch (ForbiddenException $e) {
440
+            return false;
441
+        }
442
+    }
443
+
444
+    public function isUpdatable($path) {
445
+        try {
446
+            $info = $this->getFileInfo($path);
447
+            // following windows behaviour for read-only folders: they can be written into
448
+            // (https://support.microsoft.com/en-us/kb/326549 - "cause" section)
449
+            return !$info->isHidden() && (!$info->isReadOnly() || $this->is_dir($path));
450
+        } catch (NotFoundException $e) {
451
+            return false;
452
+        } catch (ForbiddenException $e) {
453
+            return false;
454
+        }
455
+    }
456
+
457
+    public function isDeletable($path) {
458
+        try {
459
+            $info = $this->getFileInfo($path);
460
+            return !$info->isHidden() && !$info->isReadOnly();
461
+        } catch (NotFoundException $e) {
462
+            return false;
463
+        } catch (ForbiddenException $e) {
464
+            return false;
465
+        }
466
+    }
467
+
468
+    /**
469
+     * check if smbclient is installed
470
+     */
471
+    public static function checkDependencies() {
472
+        return (
473
+            (bool)\OC_Helper::findBinaryPath('smbclient')
474
+            || Server::NativeAvailable()
475
+        ) ? true : ['smbclient'];
476
+    }
477
+
478
+    /**
479
+     * Test a storage for availability
480
+     *
481
+     * @return bool
482
+     */
483
+    public function test() {
484
+        try {
485
+            return parent::test();
486
+        } catch (Exception $e) {
487
+            return false;
488
+        }
489
+    }
490
+
491
+    public function listen($path, callable $callback) {
492
+        $this->notify($path)->listen(function (IChange $change) use ($callback) {
493
+            if ($change instanceof IRenameChange) {
494
+                return $callback($change->getType(), $change->getPath(), $change->getTargetPath());
495
+            } else {
496
+                return $callback($change->getType(), $change->getPath());
497
+            }
498
+        });
499
+    }
500
+
501
+    public function notify($path) {
502
+        $path = '/' . ltrim($path, '/');
503
+        $shareNotifyHandler = $this->share->notify($this->buildPath($path));
504
+        return new SMBNotifyHandler($shareNotifyHandler, $this->root);
505
+    }
506 506
 }
Please login to merge, or discard this patch.