2012-02-18 06:56:20 +04:00
|
|
|
<?php
|
|
|
|
/**
|
2016-07-22 10:45:36 +03:00
|
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
|
|
|
*
|
2015-03-26 13:44:34 +03:00
|
|
|
* @author Adam Williamson <awilliam@redhat.com>
|
2016-05-26 20:56:05 +03:00
|
|
|
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
|
2015-03-26 13:44:34 +03:00
|
|
|
* @author Bart Visscher <bartv@thisnet.nl>
|
|
|
|
* @author Christopher Schäpers <kondou@ts.unde.re>
|
2016-05-26 20:56:05 +03:00
|
|
|
* @author Francesco Rovelli <francesco.rovelli@gmail.com>
|
2015-03-26 13:44:34 +03:00
|
|
|
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
2016-05-26 20:56:05 +03:00
|
|
|
* @author Lukas Reschke <lukas@statuscode.ch>
|
2015-03-26 13:44:34 +03:00
|
|
|
* @author Michael Gapczynski <GapczynskiM@gmail.com>
|
|
|
|
* @author Morris Jobke <hey@morrisjobke.de>
|
|
|
|
* @author Philipp Kapfer <philipp.kapfer@gmx.at>
|
2016-07-22 10:45:36 +03:00
|
|
|
* @author Robin Appelman <robin@icewind.nl>
|
2016-01-12 17:02:16 +03:00
|
|
|
* @author Robin McCorkell <robin@mccorkell.me.uk>
|
2015-03-26 13:44:34 +03:00
|
|
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
|
|
|
* @author Vincent Petry <pvince81@owncloud.com>
|
2013-07-13 19:02:07 +04:00
|
|
|
*
|
2015-03-26 13:44:34 +03:00
|
|
|
* @license AGPL-3.0
|
2013-07-13 19:02:07 +04:00
|
|
|
*
|
2015-03-26 13:44:34 +03:00
|
|
|
* This code is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
|
|
* as published by the Free Software Foundation.
|
2013-07-13 19:02:07 +04:00
|
|
|
*
|
2015-03-26 13:44:34 +03:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
2013-07-13 19:02:07 +04:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-03-26 13:44:34 +03:00
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
2013-07-13 19:02:07 +04:00
|
|
|
*
|
|
|
|
*/
|
2015-02-26 13:37:37 +03:00
|
|
|
|
2016-04-14 01:18:07 +03:00
|
|
|
namespace OCA\Files_External\Lib\Storage;
|
2012-09-07 20:30:48 +04:00
|
|
|
|
2016-02-23 19:53:19 +03:00
|
|
|
use GuzzleHttp\Exception\RequestException;
|
2015-03-10 18:30:13 +03:00
|
|
|
use Icewind\Streams\IteratorDirectory;
|
2016-03-23 16:55:03 +03:00
|
|
|
use Icewind\Streams\RetryWrapper;
|
2015-03-10 18:30:13 +03:00
|
|
|
|
2013-05-17 22:33:37 +04:00
|
|
|
set_include_path(get_include_path().PATH_SEPARATOR.
|
|
|
|
\OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src');
|
2016-04-26 17:24:30 +03:00
|
|
|
require_once 'Google/autoload.php';
|
2012-02-29 04:15:49 +04:00
|
|
|
|
2012-09-07 20:30:48 +04:00
|
|
|
class Google extends \OC\Files\Storage\Common {
|
2012-02-27 23:59:41 +04:00
|
|
|
|
2014-11-07 08:28:05 +03:00
|
|
|
private $client;
|
2012-10-12 01:06:57 +04:00
|
|
|
private $id;
|
2013-05-17 04:09:32 +04:00
|
|
|
private $service;
|
|
|
|
private $driveFiles;
|
2012-02-27 23:59:41 +04:00
|
|
|
|
2012-05-02 22:38:22 +04:00
|
|
|
private static $tempFiles = array();
|
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
// Google Doc mimetypes
|
|
|
|
const FOLDER = 'application/vnd.google-apps.folder';
|
|
|
|
const DOCUMENT = 'application/vnd.google-apps.document';
|
|
|
|
const SPREADSHEET = 'application/vnd.google-apps.spreadsheet';
|
|
|
|
const DRAWING = 'application/vnd.google-apps.drawing';
|
|
|
|
const PRESENTATION = 'application/vnd.google-apps.presentation';
|
2016-05-24 15:45:29 +03:00
|
|
|
const MAP = 'application/vnd.google-apps.map';
|
2013-05-17 04:09:32 +04:00
|
|
|
|
2012-08-14 01:07:56 +04:00
|
|
|
public function __construct($params) {
|
2013-05-17 04:09:32 +04:00
|
|
|
if (isset($params['configured']) && $params['configured'] === 'true'
|
|
|
|
&& isset($params['client_id']) && isset($params['client_secret'])
|
2012-11-30 19:27:11 +04:00
|
|
|
&& isset($params['token'])
|
|
|
|
) {
|
2014-01-29 07:57:07 +04:00
|
|
|
$this->client = new \Google_Client();
|
|
|
|
$this->client->setClientId($params['client_id']);
|
|
|
|
$this->client->setClientSecret($params['client_secret']);
|
|
|
|
$this->client->setScopes(array('https://www.googleapis.com/auth/drive'));
|
|
|
|
$this->client->setAccessToken($params['token']);
|
2014-11-08 09:52:07 +03:00
|
|
|
// if curl isn't available we're likely to run into
|
|
|
|
// https://github.com/google/google-api-php-client/issues/59
|
|
|
|
// - disable gzip to avoid it.
|
|
|
|
if (!function_exists('curl_version') || !function_exists('curl_exec')) {
|
|
|
|
$this->client->setClassConfig("Google_Http_Request", "disable_gzip", true);
|
|
|
|
}
|
2014-10-21 18:18:44 +04:00
|
|
|
// note: API connection is lazy
|
2014-01-29 07:57:07 +04:00
|
|
|
$this->service = new \Google_Service_Drive($this->client);
|
2013-05-17 04:09:32 +04:00
|
|
|
$token = json_decode($params['token'], true);
|
2013-06-05 02:07:14 +04:00
|
|
|
$this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created'];
|
2012-08-14 01:07:56 +04:00
|
|
|
} else {
|
2016-04-14 01:18:07 +03:00
|
|
|
throw new \Exception('Creating Google storage failed');
|
2012-08-14 01:07:56 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
public function getId() {
|
|
|
|
return $this->id;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2012-11-30 19:27:11 +04:00
|
|
|
/**
|
2014-11-07 08:27:12 +03:00
|
|
|
* Get the Google_Service_Drive_DriveFile object for the specified path.
|
|
|
|
* Returns false on failure.
|
2013-05-17 04:09:32 +04:00
|
|
|
* @param string $path
|
2014-11-07 08:27:12 +03:00
|
|
|
* @return \Google_Service_Drive_DriveFile|false
|
2012-11-30 19:27:11 +04:00
|
|
|
*/
|
2013-05-17 04:09:32 +04:00
|
|
|
private function getDriveFile($path) {
|
|
|
|
// Remove leading and trailing slashes
|
2013-07-23 18:50:14 +04:00
|
|
|
$path = trim($path, '/');
|
2016-06-07 17:29:23 +03:00
|
|
|
if ($path === '.') {
|
|
|
|
$path = '';
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
if (isset($this->driveFiles[$path])) {
|
|
|
|
return $this->driveFiles[$path];
|
|
|
|
} else if ($path === '') {
|
|
|
|
$root = $this->service->files->get('root');
|
|
|
|
$this->driveFiles[$path] = $root;
|
|
|
|
return $root;
|
2012-02-29 04:15:49 +04:00
|
|
|
} else {
|
2013-05-17 04:09:32 +04:00
|
|
|
// Google Drive SDK does not have methods for retrieving files by path
|
|
|
|
// Instead we must find the id of the parent folder of the file
|
|
|
|
$parentId = $this->getDriveFile('')->getId();
|
|
|
|
$folderNames = explode('/', $path);
|
|
|
|
$path = '';
|
|
|
|
// Loop through each folder of this path to get to the file
|
|
|
|
foreach ($folderNames as $name) {
|
|
|
|
// Reconstruct path from beginning
|
|
|
|
if ($path === '') {
|
|
|
|
$path .= $name;
|
|
|
|
} else {
|
|
|
|
$path .= '/'.$name;
|
|
|
|
}
|
|
|
|
if (isset($this->driveFiles[$path])) {
|
|
|
|
$parentId = $this->driveFiles[$path]->getId();
|
|
|
|
} else {
|
2015-03-31 16:30:49 +03:00
|
|
|
$q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false";
|
2013-05-17 04:09:32 +04:00
|
|
|
$result = $this->service->files->listFiles(array('q' => $q))->getItems();
|
|
|
|
if (!empty($result)) {
|
|
|
|
// Google Drive allows files with the same name, ownCloud doesn't
|
|
|
|
if (count($result) > 1) {
|
|
|
|
$this->onDuplicateFileDetected($path);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
$file = current($result);
|
|
|
|
$this->driveFiles[$path] = $file;
|
|
|
|
$parentId = $file->getId();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Google Docs have no extension in their title, so try without extension
|
|
|
|
$pos = strrpos($path, '.');
|
|
|
|
if ($pos !== false) {
|
|
|
|
$pathWithoutExt = substr($path, 0, $pos);
|
|
|
|
$file = $this->getDriveFile($pathWithoutExt);
|
2016-06-07 17:29:23 +03:00
|
|
|
if ($file && $this->isGoogleDocFile($file)) {
|
2014-01-29 07:57:07 +04:00
|
|
|
// Switch cached Google_Service_Drive_DriveFile to the correct index
|
2013-05-17 04:09:32 +04:00
|
|
|
unset($this->driveFiles[$pathWithoutExt]);
|
|
|
|
$this->driveFiles[$path] = $file;
|
|
|
|
$parentId = $file->getId();
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2012-03-24 22:49:44 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
return $this->driveFiles[$path];
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2013-07-13 19:02:07 +04:00
|
|
|
/**
|
2014-01-29 07:57:07 +04:00
|
|
|
* Set the Google_Service_Drive_DriveFile object in the cache
|
2013-07-13 19:02:07 +04:00
|
|
|
* @param string $path
|
2016-05-24 14:58:08 +03:00
|
|
|
* @param \Google_Service_Drive_DriveFile|false $file
|
2013-07-13 19:02:07 +04:00
|
|
|
*/
|
|
|
|
private function setDriveFile($path, $file) {
|
2013-07-23 18:50:14 +04:00
|
|
|
$path = trim($path, '/');
|
2013-07-13 19:02:07 +04:00
|
|
|
$this->driveFiles[$path] = $file;
|
|
|
|
if ($file === false) {
|
2016-06-07 18:40:34 +03:00
|
|
|
// Remove all children
|
2013-07-13 19:02:07 +04:00
|
|
|
$len = strlen($path);
|
|
|
|
foreach ($this->driveFiles as $key => $file) {
|
|
|
|
if (substr($key, 0, $len) === $path) {
|
2016-06-07 18:40:34 +03:00
|
|
|
unset($this->driveFiles[$key]);
|
2013-07-13 19:02:07 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
/**
|
|
|
|
* Write a log message to inform about duplicate file names
|
|
|
|
* @param string $path
|
|
|
|
*/
|
|
|
|
private function onDuplicateFileDetected($path) {
|
|
|
|
$about = $this->service->about->get();
|
|
|
|
$user = $about->getName();
|
|
|
|
\OCP\Util::writeLog('files_external',
|
|
|
|
'Ignoring duplicate file name: '.$path.' on Google Drive for Google user: '.$user,
|
2013-07-13 19:02:07 +04:00
|
|
|
\OCP\Util::INFO
|
|
|
|
);
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
/**
|
|
|
|
* Generate file extension for a Google Doc, choosing Open Document formats for download
|
|
|
|
* @param string $mimetype
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function getGoogleDocExtension($mimetype) {
|
|
|
|
if ($mimetype === self::DOCUMENT) {
|
|
|
|
return 'odt';
|
|
|
|
} else if ($mimetype === self::SPREADSHEET) {
|
|
|
|
return 'ods';
|
|
|
|
} else if ($mimetype === self::DRAWING) {
|
|
|
|
return 'jpg';
|
|
|
|
} else if ($mimetype === self::PRESENTATION) {
|
|
|
|
// Download as .odp is not available
|
|
|
|
return 'pdf';
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
2012-10-12 01:06:57 +04:00
|
|
|
}
|
2012-02-29 04:15:49 +04:00
|
|
|
|
2016-06-07 17:29:23 +03:00
|
|
|
/**
|
|
|
|
* Returns whether the given drive file is a Google Doc file
|
|
|
|
*
|
|
|
|
* @param \Google_Service_Drive_DriveFile
|
|
|
|
*
|
|
|
|
* @return true if the file is a Google Doc file, false otherwise
|
|
|
|
*/
|
|
|
|
private function isGoogleDocFile($file) {
|
|
|
|
return $this->getGoogleDocExtension($file->getMimeType()) !== '';
|
|
|
|
}
|
|
|
|
|
2012-02-27 23:59:41 +04:00
|
|
|
public function mkdir($path) {
|
2013-07-13 19:02:07 +04:00
|
|
|
if (!$this->is_dir($path)) {
|
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
2014-01-29 07:57:07 +04:00
|
|
|
$folder = new \Google_Service_Drive_DriveFile();
|
2013-07-13 19:02:07 +04:00
|
|
|
$folder->setTitle(basename($path));
|
|
|
|
$folder->setMimeType(self::FOLDER);
|
2014-01-29 07:57:07 +04:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 19:02:07 +04:00
|
|
|
$parent->setId($parentFolder->getId());
|
|
|
|
$folder->setParents(array($parent));
|
|
|
|
$result = $this->service->files->insert($folder);
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, $result);
|
|
|
|
}
|
|
|
|
return (bool)$result;
|
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
return false;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2012-02-18 06:56:20 +04:00
|
|
|
|
2012-02-27 23:59:41 +04:00
|
|
|
public function rmdir($path) {
|
2014-08-25 16:06:48 +04:00
|
|
|
if (!$this->isDeletable($path)) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-07-23 18:50:14 +04:00
|
|
|
if (trim($path, '/') === '') {
|
2013-07-13 19:02:07 +04:00
|
|
|
$dir = $this->opendir($path);
|
2013-09-04 15:06:04 +04:00
|
|
|
if(is_resource($dir)) {
|
|
|
|
while (($file = readdir($dir)) !== false) {
|
|
|
|
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
|
|
|
|
if (!$this->unlink($path.'/'.$file)) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
}
|
|
|
|
}
|
2013-09-04 15:06:04 +04:00
|
|
|
closedir($dir);
|
2013-07-13 19:02:07 +04:00
|
|
|
}
|
|
|
|
$this->driveFiles = array();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return $this->unlink($path);
|
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
public function opendir($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$folder = $this->getDriveFile($path);
|
|
|
|
if ($folder) {
|
|
|
|
$files = array();
|
|
|
|
$duplicates = array();
|
|
|
|
$pageToken = true;
|
|
|
|
while ($pageToken) {
|
|
|
|
$params = array();
|
|
|
|
if ($pageToken !== true) {
|
|
|
|
$params['pageToken'] = $pageToken;
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2015-03-31 16:30:49 +03:00
|
|
|
$params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false";
|
2013-05-17 04:09:32 +04:00
|
|
|
$children = $this->service->files->listFiles($params);
|
|
|
|
foreach ($children->getItems() as $child) {
|
|
|
|
$name = $child->getTitle();
|
|
|
|
// Check if this is a Google Doc i.e. no extension in name
|
2016-03-08 20:04:24 +03:00
|
|
|
$extension = $child->getFileExtension();
|
2016-05-24 15:45:29 +03:00
|
|
|
if (empty($extension)) {
|
|
|
|
if ($child->getMimeType() === self::MAP) {
|
|
|
|
continue; // No method known to transfer map files, ignore it
|
|
|
|
} else if ($child->getMimeType() !== self::FOLDER) {
|
|
|
|
$name .= '.'.$this->getGoogleDocExtension($child->getMimeType());
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
}
|
|
|
|
if ($path === '') {
|
|
|
|
$filepath = $name;
|
|
|
|
} else {
|
|
|
|
$filepath = $path.'/'.$name;
|
|
|
|
}
|
|
|
|
// Google Drive allows files with the same name, ownCloud doesn't
|
|
|
|
// Prevent opendir() from returning any duplicate files
|
2013-07-13 19:02:07 +04:00
|
|
|
$key = array_search($name, $files);
|
|
|
|
if ($key !== false || isset($duplicates[$filepath])) {
|
|
|
|
if (!isset($duplicates[$filepath])) {
|
|
|
|
$duplicates[$filepath] = true;
|
|
|
|
$this->setDriveFile($filepath, false);
|
|
|
|
unset($files[$key]);
|
|
|
|
$this->onDuplicateFileDetected($filepath);
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
2014-01-29 07:57:07 +04:00
|
|
|
// Cache the Google_Service_Drive_DriveFile for future use
|
2013-07-13 19:02:07 +04:00
|
|
|
$this->setDriveFile($filepath, $child);
|
2013-05-17 04:09:32 +04:00
|
|
|
$files[] = $name;
|
2012-03-24 22:49:44 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
$pageToken = $children->getNextPageToken();
|
|
|
|
}
|
2015-03-10 18:30:13 +03:00
|
|
|
return IteratorDirectory::wrap($files);
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2012-02-29 04:15:49 +04:00
|
|
|
public function stat($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
$stat = array();
|
|
|
|
if ($this->filetype($path) === 'dir') {
|
|
|
|
$stat['size'] = 0;
|
|
|
|
} else {
|
2013-05-17 19:42:14 +04:00
|
|
|
// Check if this is a Google Doc
|
2016-06-07 17:29:23 +03:00
|
|
|
if ($this->isGoogleDocFile($file)) {
|
2013-05-17 19:42:14 +04:00
|
|
|
// Return unknown file size
|
2014-08-19 16:05:08 +04:00
|
|
|
$stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN;
|
2013-05-17 19:42:14 +04:00
|
|
|
} else {
|
|
|
|
$stat['size'] = $file->getFileSize();
|
|
|
|
}
|
2012-11-30 19:27:11 +04:00
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
$stat['atime'] = strtotime($file->getLastViewedByMeDate());
|
|
|
|
$stat['mtime'] = strtotime($file->getModifiedDate());
|
|
|
|
$stat['ctime'] = strtotime($file->getCreatedDate());
|
2012-03-24 22:49:44 +04:00
|
|
|
return $stat;
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-03-24 22:49:44 +04:00
|
|
|
}
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
public function filetype($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
if ($path === '') {
|
2012-02-29 04:15:49 +04:00
|
|
|
return 'dir';
|
2012-11-30 19:27:11 +04:00
|
|
|
} else {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
if ($file->getMimeType() === self::FOLDER) {
|
|
|
|
return 'dir';
|
|
|
|
} else {
|
|
|
|
return 'file';
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-25 01:42:07 +04:00
|
|
|
public function isUpdatable($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
return $file->getEditable();
|
2012-11-30 19:27:11 +04:00
|
|
|
} else {
|
2013-05-17 04:09:32 +04:00
|
|
|
return false;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2012-02-27 23:59:41 +04:00
|
|
|
public function file_exists($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
return (bool)$this->getDriveFile($path);
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2012-02-27 23:59:41 +04:00
|
|
|
public function unlink($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = $this->service->files->trash($file->getId());
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, false);
|
|
|
|
}
|
|
|
|
return (bool)$result;
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-29 04:15:49 +04:00
|
|
|
public function rename($path1, $path2) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path1);
|
|
|
|
if ($file) {
|
2016-02-15 18:49:12 +03:00
|
|
|
$newFile = $this->getDriveFile($path2);
|
2013-05-17 04:09:32 +04:00
|
|
|
if (dirname($path1) === dirname($path2)) {
|
2016-02-15 18:49:12 +03:00
|
|
|
if ($newFile) {
|
|
|
|
// rename to the name of the target file, could be an office file without extension
|
|
|
|
$file->setTitle($newFile->getTitle());
|
|
|
|
} else {
|
|
|
|
$file->setTitle(basename(($path2)));
|
|
|
|
}
|
2012-05-02 22:38:22 +04:00
|
|
|
} else {
|
2013-05-17 04:09:32 +04:00
|
|
|
// Change file parent
|
|
|
|
$parentFolder2 = $this->getDriveFile(dirname($path2));
|
|
|
|
if ($parentFolder2) {
|
2014-01-29 07:57:07 +04:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-05-17 04:09:32 +04:00
|
|
|
$parent->setId($parentFolder2->getId());
|
|
|
|
$file->setParents(array($parent));
|
2013-07-13 19:02:07 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
|
|
|
}
|
google: delete original after successful rename
In GDrive, filenames aren't unique, and directories are just
special files - so you can have multiple files with the same
name, multiple directories with the same name, and even files
with the same names as directories.
OC doesn't handle this at all, though, and just wants to act
as if file and directory names *are* unique. So when renaming,
we must check if there's an existing object with the same
file or directory name before we commit the rename, and
explicitly delete it if the rename is successful. (Other
providers like dropbox do the same for files, but intentionally
don't do it for directories; we really need to do it for
directories too.)
A good way to observe this is to run the storage unit tests
and look at the state of the Drive afterwards. Without this
commit, there will be several copies of all the test files
and directories. After this commit, there's just one of each.
We can't just say "hey, Drive lets us do this, what's the
problem?" because we don't handle multiple-objects, same-name
cases - getDriveFile() just bails and prints an error if it
searches for the file or directory with a given name and gets
multiple results.
2014-11-11 00:32:20 +03:00
|
|
|
// We need to get the object for the existing file with the same
|
|
|
|
// name (if there is one) before we do the patch. If oldfile
|
|
|
|
// exists and is a directory we have to delete it before we
|
|
|
|
// do the rename too.
|
|
|
|
$oldfile = $this->getDriveFile($path2);
|
|
|
|
if ($oldfile && $this->is_dir($path2)) {
|
|
|
|
$this->rmdir($path2);
|
|
|
|
$oldfile = false;
|
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = $this->service->files->patch($file->getId(), $file);
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path1, false);
|
|
|
|
$this->setDriveFile($path2, $result);
|
2016-02-15 18:49:12 +03:00
|
|
|
if ($oldfile && $newFile) {
|
|
|
|
// only delete if they have a different id (same id can happen for part files)
|
|
|
|
if ($newFile->getId() !== $oldfile->getId()) {
|
|
|
|
$this->service->files->delete($oldfile->getId());
|
|
|
|
}
|
google: delete original after successful rename
In GDrive, filenames aren't unique, and directories are just
special files - so you can have multiple files with the same
name, multiple directories with the same name, and even files
with the same names as directories.
OC doesn't handle this at all, though, and just wants to act
as if file and directory names *are* unique. So when renaming,
we must check if there's an existing object with the same
file or directory name before we commit the rename, and
explicitly delete it if the rename is successful. (Other
providers like dropbox do the same for files, but intentionally
don't do it for directories; we really need to do it for
directories too.)
A good way to observe this is to run the storage unit tests
and look at the state of the Drive afterwards. Without this
commit, there will be several copies of all the test files
and directories. After this commit, there's just one of each.
We can't just say "hey, Drive lets us do this, what's the
problem?" because we don't handle multiple-objects, same-name
cases - getDriveFile() just bails and prints an error if it
searches for the file or directory with a given name and gets
multiple results.
2014-11-11 00:32:20 +03:00
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
}
|
|
|
|
return (bool)$result;
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2012-02-29 04:15:49 +04:00
|
|
|
public function fopen($path, $mode) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$pos = strrpos($path, '.');
|
|
|
|
if ($pos !== false) {
|
|
|
|
$ext = substr($path, $pos);
|
|
|
|
} else {
|
|
|
|
$ext = '';
|
|
|
|
}
|
2012-05-02 22:38:22 +04:00
|
|
|
switch ($mode) {
|
|
|
|
case 'r':
|
|
|
|
case 'rb':
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
$exportLinks = $file->getExportLinks();
|
|
|
|
$mimetype = $this->getMimeType($path);
|
|
|
|
$downloadUrl = null;
|
|
|
|
if ($exportLinks && isset($exportLinks[$mimetype])) {
|
|
|
|
$downloadUrl = $exportLinks[$mimetype];
|
|
|
|
} else {
|
|
|
|
$downloadUrl = $file->getDownloadUrl();
|
|
|
|
}
|
|
|
|
if (isset($downloadUrl)) {
|
2014-01-29 07:57:07 +04:00
|
|
|
$request = new \Google_Http_Request($downloadUrl, 'GET', null, null);
|
2016-01-14 19:52:30 +03:00
|
|
|
$httpRequest = $this->client->getAuth()->sign($request);
|
|
|
|
// the library's service doesn't support streaming, so we use Guzzle instead
|
|
|
|
$client = \OC::$server->getHTTPClientService()->newClient();
|
|
|
|
try {
|
2016-03-23 16:55:03 +03:00
|
|
|
$response = $client->get($downloadUrl, [
|
2016-01-14 19:52:30 +03:00
|
|
|
'headers' => $httpRequest->getRequestHeaders(),
|
2016-03-23 16:55:03 +03:00
|
|
|
'stream' => true,
|
2016-05-24 14:44:43 +03:00
|
|
|
'verify' => realpath(__DIR__ . '/../../../3rdparty/google-api-php-client/src/Google/IO/cacerts.pem'),
|
2016-01-14 19:52:30 +03:00
|
|
|
]);
|
|
|
|
} catch (RequestException $e) {
|
2016-02-25 12:12:31 +03:00
|
|
|
if(!is_null($e->getResponse())) {
|
|
|
|
if ($e->getResponse()->getStatusCode() === 404) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
throw $e;
|
|
|
|
}
|
2016-01-14 19:52:30 +03:00
|
|
|
} else {
|
|
|
|
throw $e;
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
}
|
2016-01-14 19:52:30 +03:00
|
|
|
|
2016-03-23 16:55:03 +03:00
|
|
|
$handle = $response->getBody();
|
|
|
|
return RetryWrapper::wrap($handle);
|
2013-05-17 04:09:32 +04:00
|
|
|
}
|
2012-05-02 22:38:22 +04:00
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
return false;
|
2012-05-02 22:38:22 +04:00
|
|
|
case 'w':
|
|
|
|
case 'wb':
|
|
|
|
case 'a':
|
|
|
|
case 'ab':
|
|
|
|
case 'r+':
|
|
|
|
case 'w+':
|
|
|
|
case 'wb+':
|
|
|
|
case 'a+':
|
|
|
|
case 'x':
|
|
|
|
case 'x+':
|
|
|
|
case 'c':
|
|
|
|
case 'c+':
|
2015-08-19 00:49:29 +03:00
|
|
|
$tmpFile = \OCP\Files::tmpFile($ext);
|
2013-01-28 18:34:15 +04:00
|
|
|
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
|
2012-05-02 22:38:22 +04:00
|
|
|
if ($this->file_exists($path)) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$source = $this->fopen($path, 'rb');
|
2012-05-02 22:38:22 +04:00
|
|
|
file_put_contents($tmpFile, $source);
|
|
|
|
}
|
|
|
|
self::$tempFiles[$tmpFile] = $path;
|
|
|
|
return fopen('close://'.$tmpFile, $mode);
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
|
|
|
|
2012-05-02 22:38:22 +04:00
|
|
|
public function writeBack($tmpFile) {
|
|
|
|
if (isset(self::$tempFiles[$tmpFile])) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$path = self::$tempFiles[$tmpFile];
|
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
2015-08-19 00:49:29 +03:00
|
|
|
$mimetype = \OC::$server->getMimeTypeDetector()->detect($tmpFile);
|
2013-05-17 04:09:32 +04:00
|
|
|
$params = array(
|
|
|
|
'mimeType' => $mimetype,
|
2016-03-17 14:36:47 +03:00
|
|
|
'uploadType' => 'media'
|
2013-05-17 04:09:32 +04:00
|
|
|
);
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = false;
|
2016-03-17 14:36:47 +03:00
|
|
|
|
|
|
|
$chunkSizeBytes = 10 * 1024 * 1024;
|
|
|
|
|
|
|
|
$useChunking = false;
|
|
|
|
$size = filesize($tmpFile);
|
|
|
|
if ($size > $chunkSizeBytes) {
|
|
|
|
$useChunking = true;
|
|
|
|
} else {
|
|
|
|
$params['data'] = file_get_contents($tmpFile);
|
|
|
|
}
|
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
if ($this->file_exists($path)) {
|
2013-07-13 19:02:07 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
2016-03-17 14:36:47 +03:00
|
|
|
$this->client->setDefer($useChunking);
|
2016-03-16 19:57:54 +03:00
|
|
|
$request = $this->service->files->update($file->getId(), $file, $params);
|
2012-05-02 22:38:22 +04:00
|
|
|
} else {
|
2014-01-29 07:57:07 +04:00
|
|
|
$file = new \Google_Service_Drive_DriveFile();
|
2013-07-13 19:02:07 +04:00
|
|
|
$file->setTitle(basename($path));
|
|
|
|
$file->setMimeType($mimetype);
|
2014-01-29 07:57:07 +04:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 19:02:07 +04:00
|
|
|
$parent->setId($parentFolder->getId());
|
|
|
|
$file->setParents(array($parent));
|
2016-03-17 14:36:47 +03:00
|
|
|
$this->client->setDefer($useChunking);
|
2016-03-16 19:57:54 +03:00
|
|
|
$request = $this->service->files->insert($file, $params);
|
2013-07-13 19:02:07 +04:00
|
|
|
}
|
2016-03-16 19:57:54 +03:00
|
|
|
|
2016-03-17 14:36:47 +03:00
|
|
|
if ($useChunking) {
|
|
|
|
// Create a media file upload to represent our upload process.
|
|
|
|
$media = new \Google_Http_MediaFileUpload(
|
|
|
|
$this->client,
|
|
|
|
$request,
|
|
|
|
'text/plain',
|
|
|
|
null,
|
|
|
|
true,
|
|
|
|
$chunkSizeBytes
|
|
|
|
);
|
|
|
|
$media->setFileSize($size);
|
|
|
|
|
|
|
|
// Upload the various chunks. $status will be false until the process is
|
|
|
|
// complete.
|
|
|
|
$status = false;
|
|
|
|
$handle = fopen($tmpFile, 'rb');
|
|
|
|
while (!$status && !feof($handle)) {
|
|
|
|
$chunk = fread($handle, $chunkSizeBytes);
|
|
|
|
$status = $media->nextChunk($chunk);
|
|
|
|
}
|
2016-03-16 19:57:54 +03:00
|
|
|
|
2016-03-17 14:36:47 +03:00
|
|
|
// The final value of $status will be the data from the API for the object
|
|
|
|
// that has been uploaded.
|
|
|
|
$result = false;
|
|
|
|
if ($status !== false) {
|
|
|
|
$result = $status;
|
|
|
|
}
|
2016-03-16 19:57:54 +03:00
|
|
|
|
2016-03-17 14:36:47 +03:00
|
|
|
fclose($handle);
|
|
|
|
} else {
|
|
|
|
$result = $request;
|
2016-03-16 19:57:54 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reset to the client to execute requests immediately in the future.
|
|
|
|
$this->client->setDefer(false);
|
|
|
|
|
2013-07-13 19:02:07 +04:00
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, $result);
|
2012-05-02 22:38:22 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
unlink($tmpFile);
|
2012-05-02 22:38:22 +04:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2013-05-17 04:09:32 +04:00
|
|
|
public function getMimeType($path) {
|
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
$mimetype = $file->getMimeType();
|
|
|
|
// Convert Google Doc mimetypes, choosing Open Document formats for download
|
|
|
|
if ($mimetype === self::FOLDER) {
|
2012-02-29 04:15:49 +04:00
|
|
|
return 'httpd/unix-directory';
|
2013-05-17 04:09:32 +04:00
|
|
|
} else if ($mimetype === self::DOCUMENT) {
|
|
|
|
return 'application/vnd.oasis.opendocument.text';
|
|
|
|
} else if ($mimetype === self::SPREADSHEET) {
|
|
|
|
return 'application/x-vnd.oasis.opendocument.spreadsheet';
|
|
|
|
} else if ($mimetype === self::DRAWING) {
|
|
|
|
return 'image/jpeg';
|
|
|
|
} else if ($mimetype === self::PRESENTATION) {
|
|
|
|
// Download as .odp is not available
|
|
|
|
return 'application/pdf';
|
2012-02-29 04:15:49 +04:00
|
|
|
} else {
|
2016-02-15 19:09:08 +03:00
|
|
|
// use extension-based detection, could be an encrypted file
|
|
|
|
return parent::getMimeType($path);
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
2013-05-17 04:09:32 +04:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 04:15:49 +04:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2012-02-27 23:59:41 +04:00
|
|
|
public function free_space($path) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$about = $this->service->about->get();
|
|
|
|
return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2012-08-29 10:42:49 +04:00
|
|
|
|
2012-03-24 22:49:44 +04:00
|
|
|
public function touch($path, $mtime = null) {
|
2013-05-17 04:09:32 +04:00
|
|
|
$file = $this->getDriveFile($path);
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = false;
|
2013-05-17 04:09:32 +04:00
|
|
|
if ($file) {
|
|
|
|
if (isset($mtime)) {
|
2014-11-08 11:38:00 +03:00
|
|
|
// This is just RFC3339, but frustratingly, GDrive's API *requires*
|
|
|
|
// the fractions portion be present, while no handy PHP constant
|
|
|
|
// for RFC3339 or ISO8601 includes it. So we do it ourselves.
|
|
|
|
$file->setModifiedDate(date('Y-m-d\TH:i:s.uP', $mtime));
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = $this->service->files->patch($file->getId(), $file, array(
|
2013-05-17 04:09:32 +04:00
|
|
|
'setModifiedDate' => true,
|
|
|
|
));
|
|
|
|
} else {
|
2013-07-13 19:02:07 +04:00
|
|
|
$result = $this->service->files->touch($file->getId());
|
2013-05-17 04:09:32 +04:00
|
|
|
}
|
|
|
|
} else {
|
2013-07-13 19:02:07 +04:00
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
2014-01-29 07:57:07 +04:00
|
|
|
$file = new \Google_Service_Drive_DriveFile();
|
2013-07-13 19:02:07 +04:00
|
|
|
$file->setTitle(basename($path));
|
2014-01-29 07:57:07 +04:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 19:02:07 +04:00
|
|
|
$parent->setId($parentFolder->getId());
|
|
|
|
$file->setParents(array($parent));
|
|
|
|
$result = $this->service->files->insert($file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, $result);
|
2013-05-17 04:09:32 +04:00
|
|
|
}
|
2013-07-13 19:02:07 +04:00
|
|
|
return (bool)$result;
|
2012-02-27 23:59:41 +04:00
|
|
|
}
|
2012-02-29 04:15:49 +04:00
|
|
|
|
2012-12-28 21:00:48 +04:00
|
|
|
public function test() {
|
|
|
|
if ($this->free_space('')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-05-31 05:45:16 +04:00
|
|
|
public function hasUpdated($path, $time) {
|
2014-02-13 19:28:49 +04:00
|
|
|
$appConfig = \OC::$server->getAppConfig();
|
2013-05-31 05:45:16 +04:00
|
|
|
if ($this->is_file($path)) {
|
|
|
|
return parent::hasUpdated($path, $time);
|
|
|
|
} else {
|
|
|
|
// Google Drive doesn't change modified times of folders when files inside are updated
|
|
|
|
// Instead we use the Changes API to see if folders have been updated, and it's a pain
|
|
|
|
$folder = $this->getDriveFile($path);
|
|
|
|
if ($folder) {
|
|
|
|
$result = false;
|
|
|
|
$folderId = $folder->getId();
|
2014-02-13 19:28:49 +04:00
|
|
|
$startChangeId = $appConfig->getValue('files_external', $this->getId().'cId');
|
2013-05-31 05:45:16 +04:00
|
|
|
$params = array(
|
|
|
|
'includeDeleted' => true,
|
|
|
|
'includeSubscribed' => true,
|
|
|
|
);
|
|
|
|
if (isset($startChangeId)) {
|
|
|
|
$startChangeId = (int)$startChangeId;
|
|
|
|
$largestChangeId = $startChangeId;
|
|
|
|
$params['startChangeId'] = $startChangeId + 1;
|
|
|
|
} else {
|
|
|
|
$largestChangeId = 0;
|
|
|
|
}
|
|
|
|
$pageToken = true;
|
|
|
|
while ($pageToken) {
|
|
|
|
if ($pageToken !== true) {
|
|
|
|
$params['pageToken'] = $pageToken;
|
|
|
|
}
|
|
|
|
$changes = $this->service->changes->listChanges($params);
|
|
|
|
if ($largestChangeId === 0 || $largestChangeId === $startChangeId) {
|
|
|
|
$largestChangeId = $changes->getLargestChangeId();
|
|
|
|
}
|
|
|
|
if (isset($startChangeId)) {
|
|
|
|
// Check if a file in this folder has been updated
|
|
|
|
// There is no way to filter by folder at the API level...
|
|
|
|
foreach ($changes->getItems() as $change) {
|
2013-07-13 19:02:07 +04:00
|
|
|
$file = $change->getFile();
|
|
|
|
if ($file) {
|
|
|
|
foreach ($file->getParents() as $parent) {
|
|
|
|
if ($parent->getId() === $folderId) {
|
|
|
|
$result = true;
|
|
|
|
// Check if there are changes in different folders
|
|
|
|
} else if ($change->getId() <= $largestChangeId) {
|
|
|
|
// Decrement id so this change is fetched when called again
|
|
|
|
$largestChangeId = $change->getId();
|
|
|
|
$largestChangeId--;
|
|
|
|
}
|
2013-05-31 05:45:16 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$pageToken = $changes->getNextPageToken();
|
|
|
|
} else {
|
|
|
|
// Assuming the initial scan just occurred and changes are negligible
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-02-13 19:28:49 +04:00
|
|
|
$appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId);
|
2013-05-31 05:45:16 +04:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-02 17:44:56 +04:00
|
|
|
/**
|
|
|
|
* check if curl is installed
|
|
|
|
*/
|
|
|
|
public static function checkDependencies() {
|
2015-03-12 23:43:41 +03:00
|
|
|
return true;
|
2013-08-02 17:44:56 +04:00
|
|
|
}
|
|
|
|
|
2013-08-18 13:02:08 +04:00
|
|
|
}
|