Completed
Pull Request — master (#31)
by Blizzz
13:09 queued 04:36
created

Google::isGoogleDocFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 2
b 2
f 0
1
<?php
2
/**
3
 * @author Adam Williamson <[email protected]>
4
 * @author Arthur Schiwon <[email protected]>
5
 * @author Bart Visscher <[email protected]>
6
 * @author Christopher Schäpers <[email protected]>
7
 * @author Francesco Rovelli <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Michael Gapczynski <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Philipp Kapfer <[email protected]>
13
 * @author Robin Appelman <[email protected]>
14
 * @author Robin McCorkell <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @copyright Copyright (c) 2016, ownCloud, Inc.
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
35
namespace OCA\Files_External\Lib\Storage;
36
37
use GuzzleHttp\Exception\RequestException;
38
use Icewind\Streams\IteratorDirectory;
39
use Icewind\Streams\RetryWrapper;
40
41
set_include_path(get_include_path().PATH_SEPARATOR.
42
	\OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src');
43
require_once 'Google/autoload.php';
44
45
class Google extends \OC\Files\Storage\Common {
46
47
	private $client;
48
	private $id;
49
	private $service;
50
	private $driveFiles;
51
52
	private static $tempFiles = array();
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
			// Set all child paths as false
172
			$len = strlen($path);
173
			foreach ($this->driveFiles as $key => $file) {
174
				if (substr($key, 0, $len) === $path) {
175
					$this->driveFiles[$key] = false;
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 View Code Duplication
		if ($mimetype === self::DOCUMENT) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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;
0 ignored issues
show
Unused Code introduced by
$downloadUrl is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\RequestException does not exist. Did you forget a USE statement, or did you not list all dependencies?

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

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

Loading history...
468 View Code Duplication
							if(!is_null($e->getResponse())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Files::tmpFile() has been deprecated with message: 8.1.0 use getTemporaryFile() of \OCP\ITempManager - \OC::$server->getTempManager()

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

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

Loading history...
497
				\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
498
				if ($this->file_exists($path)) {
499
					$source = $this->fopen($path, 'rb');
500
					file_put_contents($tmpFile, $source);
501
				}
502
				self::$tempFiles[$tmpFile] = $path;
503
				return fopen('close://'.$tmpFile, $mode);
504
		}
505
	}
506
507
	public function writeBack($tmpFile) {
508
		if (isset(self::$tempFiles[$tmpFile])) {
509
			$path = self::$tempFiles[$tmpFile];
510
			$parentFolder = $this->getDriveFile(dirname($path));
511
			if ($parentFolder) {
512
				$mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
513
				$params = array(
514
					'mimeType' => $mimetype,
515
					'uploadType' => 'media'
516
				);
517
				$result = false;
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
518
519
				$chunkSizeBytes = 10 * 1024 * 1024;
520
521
				$useChunking = false;
522
				$size = filesize($tmpFile);
523
				if ($size > $chunkSizeBytes) {
524
					$useChunking = true;
525
				} else {
526
					$params['data'] = file_get_contents($tmpFile);
527
				}
528
529
				if ($this->file_exists($path)) {
530
					$file = $this->getDriveFile($path);
531
					$this->client->setDefer($useChunking);
532
					$request = $this->service->files->update($file->getId(), $file, $params);
533 View Code Duplication
				} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
534
					$file = new \Google_Service_Drive_DriveFile();
535
					$file->setTitle(basename($path));
536
					$file->setMimeType($mimetype);
537
					$parent = new \Google_Service_Drive_ParentReference();
538
					$parent->setId($parentFolder->getId());
539
					$file->setParents(array($parent));
540
					$this->client->setDefer($useChunking);
541
					$request = $this->service->files->insert($file, $params);
542
				}
543
544
				if ($useChunking) {
545
					// Create a media file upload to represent our upload process.
546
					$media = new \Google_Http_MediaFileUpload(
547
						$this->client,
548
						$request,
549
						'text/plain',
550
						null,
551
						true,
552
						$chunkSizeBytes
553
					);
554
					$media->setFileSize($size);
555
556
					// Upload the various chunks. $status will be false until the process is
557
					// complete.
558
					$status = false;
559
					$handle = fopen($tmpFile, 'rb');
560
					while (!$status && !feof($handle)) {
561
						$chunk = fread($handle, $chunkSizeBytes);
562
						$status = $media->nextChunk($chunk);
563
					}
564
565
					// The final value of $status will be the data from the API for the object
566
					// that has been uploaded.
567
					$result = false;
568
					if ($status !== false) {
569
						$result = $status;
570
					}
571
572
					fclose($handle);
573
				} else {
574
					$result = $request;
575
				}
576
577
				// Reset to the client to execute requests immediately in the future.
578
				$this->client->setDefer(false);
579
580
				if ($result) {
581
					$this->setDriveFile($path, $result);
582
				}
583
			}
584
			unlink($tmpFile);
585
		}
586
	}
587
588
	public function getMimeType($path) {
589
		$file = $this->getDriveFile($path);
590
		if ($file) {
591
			$mimetype = $file->getMimeType();
592
			// Convert Google Doc mimetypes, choosing Open Document formats for download
593
			if ($mimetype === self::FOLDER) {
594
				return 'httpd/unix-directory';
595 View Code Duplication
			} else if ($mimetype === self::DOCUMENT) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
596
				return 'application/vnd.oasis.opendocument.text';
597
			} else if ($mimetype === self::SPREADSHEET) {
598
				return 'application/x-vnd.oasis.opendocument.spreadsheet';
599
			} else if ($mimetype === self::DRAWING) {
600
				return 'image/jpeg';
601
			} else if ($mimetype === self::PRESENTATION) {
602
				// Download as .odp is not available
603
				return 'application/pdf';
604
			} else {
605
				// use extension-based detection, could be an encrypted file
606
				return parent::getMimeType($path);
607
			}
608
		} else {
609
			return false;
610
		}
611
	}
612
613
	public function free_space($path) {
614
		$about = $this->service->about->get();
615
		return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
0 ignored issues
show
Bug Compatibility introduced by
The expression $about->getQuotaBytesTot...t->getQuotaBytesUsed(); of type integer|double adds the type double to the return on line 615 which is incompatible with the return type declared by the interface OCP\Files\Storage::free_space of type integer|false.
Loading history...
616
	}
617
618
	public function touch($path, $mtime = null) {
619
		$file = $this->getDriveFile($path);
620
		$result = false;
621
		if ($file) {
622
			if (isset($mtime)) {
623
				// This is just RFC3339, but frustratingly, GDrive's API *requires*
624
				// the fractions portion be present, while no handy PHP constant
625
				// for RFC3339 or ISO8601 includes it. So we do it ourselves.
626
				$file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime));
627
				$result = $this->service->files->patch($file->getId(), $file, array(
628
					'setModifiedDate' => true,
629
				));
630
			} else {
631
				$result = $this->service->files->touch($file->getId());
632
			}
633 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
634
			$parentFolder = $this->getDriveFile(dirname($path));
635
			if ($parentFolder) {
636
				$file = new \Google_Service_Drive_DriveFile();
637
				$file->setTitle(basename($path));
638
				$parent = new \Google_Service_Drive_ParentReference();
639
				$parent->setId($parentFolder->getId());
640
				$file->setParents(array($parent));
641
				$result = $this->service->files->insert($file);
642
			}
643
		}
644
		if ($result) {
645
			$this->setDriveFile($path, $result);
646
		}
647
		return (bool)$result;
648
	}
649
650
	public function test() {
651
		if ($this->free_space('')) {
652
			return true;
653
		}
654
		return false;
655
	}
656
657
	public function hasUpdated($path, $time) {
658
		$appConfig = \OC::$server->getAppConfig();
659
		if ($this->is_file($path)) {
660
			return parent::hasUpdated($path, $time);
661
		} else {
662
			// Google Drive doesn't change modified times of folders when files inside are updated
663
			// Instead we use the Changes API to see if folders have been updated, and it's a pain
664
			$folder = $this->getDriveFile($path);
665
			if ($folder) {
666
				$result = false;
667
				$folderId = $folder->getId();
668
				$startChangeId = $appConfig->getValue('files_external', $this->getId().'cId');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::getValue() has been deprecated with message: 8.0.0 use method getAppValue of \OCP\IConfig This function gets a value from the appconfig table. If the key does
not exist the default value will be returned

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

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

Loading history...
669
				$params = array(
670
					'includeDeleted' => true,
671
					'includeSubscribed' => true,
672
				);
673
				if (isset($startChangeId)) {
674
					$startChangeId = (int)$startChangeId;
675
					$largestChangeId = $startChangeId;
676
					$params['startChangeId'] = $startChangeId + 1;
677
				} else {
678
					$largestChangeId = 0;
679
				}
680
				$pageToken = true;
681
				while ($pageToken) {
682
					if ($pageToken !== true) {
683
						$params['pageToken'] = $pageToken;
684
					}
685
					$changes = $this->service->changes->listChanges($params);
686
					if ($largestChangeId === 0 || $largestChangeId === $startChangeId) {
687
						$largestChangeId = $changes->getLargestChangeId();
688
					}
689
					if (isset($startChangeId)) {
690
						// Check if a file in this folder has been updated
691
						// There is no way to filter by folder at the API level...
692
						foreach ($changes->getItems() as $change) {
693
							$file = $change->getFile();
694
							if ($file) {
695
								foreach ($file->getParents() as $parent) {
696
									if ($parent->getId() === $folderId) {
697
										$result = true;
698
									// Check if there are changes in different folders
699
									} else if ($change->getId() <= $largestChangeId) {
700
										// Decrement id so this change is fetched when called again
701
										$largestChangeId = $change->getId();
702
										$largestChangeId--;
703
									}
704
								}
705
							}
706
						}
707
						$pageToken = $changes->getNextPageToken();
708
					} else {
709
						// Assuming the initial scan just occurred and changes are negligible
710
						break;
711
					}
712
				}
713
				$appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\IAppConfig::setValue() has been deprecated with message: 8.0.0 use method setAppValue of \OCP\IConfig Sets a value. If the key did not exist before it will be created.

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

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

Loading history...
714
				return $result;
715
			}
716
		}
717
		return false;
718
	}
719
720
	/**
721
	 * check if curl is installed
722
	 */
723
	public static function checkDependencies() {
724
		return true;
725
	}
726
727
}
728