2012-02-18 02:56:20 +00:00
|
|
|
<?php
|
|
|
|
/**
|
2015-03-26 10:44:34 +00:00
|
|
|
* @author Adam Williamson <awilliam@redhat.com>
|
|
|
|
* @author Arthur Schiwon <blizzz@owncloud.com>
|
|
|
|
* @author Bart Visscher <bartv@thisnet.nl>
|
|
|
|
* @author Christopher Schäpers <kondou@ts.unde.re>
|
|
|
|
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
|
|
|
* @author Michael Gapczynski <GapczynskiM@gmail.com>
|
|
|
|
* @author Morris Jobke <hey@morrisjobke.de>
|
|
|
|
* @author Philipp Kapfer <philipp.kapfer@gmx.at>
|
|
|
|
* @author Robin Appelman <icewind@owncloud.com>
|
|
|
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
|
|
|
* @author Vincent Petry <pvince81@owncloud.com>
|
2013-07-13 15:02:07 +00:00
|
|
|
*
|
2015-03-26 10:44:34 +00:00
|
|
|
* @copyright Copyright (c) 2015, ownCloud, Inc.
|
|
|
|
* @license AGPL-3.0
|
2013-07-13 15:02:07 +00:00
|
|
|
*
|
2015-03-26 10:44:34 +00: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 15:02:07 +00:00
|
|
|
*
|
2015-03-26 10:44:34 +00:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
2013-07-13 15:02:07 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-03-26 10:44:34 +00: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 15:02:07 +00:00
|
|
|
*
|
|
|
|
*/
|
2015-02-26 10:37:37 +00:00
|
|
|
|
2012-09-07 16:30:48 +00:00
|
|
|
namespace OC\Files\Storage;
|
|
|
|
|
2013-05-17 18:33:37 +00:00
|
|
|
set_include_path(get_include_path().PATH_SEPARATOR.
|
|
|
|
\OC_App::getAppPath('files_external').'/3rdparty/google-api-php-client/src');
|
2014-01-29 03:57:07 +00:00
|
|
|
require_once 'Google/Client.php';
|
|
|
|
require_once 'Google/Service/Drive.php';
|
2012-02-29 00:15:49 +00:00
|
|
|
|
2012-09-07 16:30:48 +00:00
|
|
|
class Google extends \OC\Files\Storage\Common {
|
2012-02-27 19:59:41 +00:00
|
|
|
|
2014-11-07 05:28:05 +00:00
|
|
|
private $client;
|
2012-10-11 21:06:57 +00:00
|
|
|
private $id;
|
2013-05-17 00:09:32 +00:00
|
|
|
private $service;
|
|
|
|
private $driveFiles;
|
2012-02-27 19:59:41 +00:00
|
|
|
|
2012-05-02 18:38:22 +00:00
|
|
|
private static $tempFiles = array();
|
|
|
|
|
2013-05-17 00:09:32 +00: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';
|
|
|
|
|
2012-08-13 21:07:56 +00:00
|
|
|
public function __construct($params) {
|
2013-05-17 00:09:32 +00:00
|
|
|
if (isset($params['configured']) && $params['configured'] === 'true'
|
|
|
|
&& isset($params['client_id']) && isset($params['client_secret'])
|
2012-11-30 15:27:11 +00:00
|
|
|
&& isset($params['token'])
|
|
|
|
) {
|
2014-01-29 03:57:07 +00: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 06:52:07 +00: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 14:18:44 +00:00
|
|
|
// note: API connection is lazy
|
2014-01-29 03:57:07 +00:00
|
|
|
$this->service = new \Google_Service_Drive($this->client);
|
2013-05-17 00:09:32 +00:00
|
|
|
$token = json_decode($params['token'], true);
|
2013-06-04 22:07:14 +00:00
|
|
|
$this->id = 'google::'.substr($params['client_id'], 0, 30).$token['created'];
|
2012-08-13 21:07:56 +00:00
|
|
|
} else {
|
2012-09-07 16:30:48 +00:00
|
|
|
throw new \Exception('Creating \OC\Files\Storage\Google storage failed');
|
2012-08-13 21:07:56 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2013-05-17 00:09:32 +00:00
|
|
|
public function getId() {
|
|
|
|
return $this->id;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2012-11-30 15:27:11 +00:00
|
|
|
/**
|
2014-11-07 05:27:12 +00:00
|
|
|
* Get the Google_Service_Drive_DriveFile object for the specified path.
|
|
|
|
* Returns false on failure.
|
2013-05-17 00:09:32 +00:00
|
|
|
* @param string $path
|
2014-11-07 05:27:12 +00:00
|
|
|
* @return \Google_Service_Drive_DriveFile|false
|
2012-11-30 15:27:11 +00:00
|
|
|
*/
|
2013-05-17 00:09:32 +00:00
|
|
|
private function getDriveFile($path) {
|
|
|
|
// Remove leading and trailing slashes
|
2013-07-23 14:50:14 +00:00
|
|
|
$path = trim($path, '/');
|
2013-05-17 00:09:32 +00: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 00:15:49 +00:00
|
|
|
} else {
|
2013-05-17 00:09:32 +00: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 13:30:49 +00:00
|
|
|
$q = "title='" . str_replace("'","\\'", $name) . "' and '" . str_replace("'","\\'", $parentId) . "' in parents and trashed = false";
|
2013-05-17 00:09:32 +00: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);
|
|
|
|
if ($file) {
|
2014-01-29 03:57:07 +00:00
|
|
|
// Switch cached Google_Service_Drive_DriveFile to the correct index
|
2013-05-17 00:09:32 +00:00
|
|
|
unset($this->driveFiles[$pathWithoutExt]);
|
|
|
|
$this->driveFiles[$path] = $file;
|
|
|
|
$parentId = $file->getId();
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2012-03-24 18:49:44 +00:00
|
|
|
}
|
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
return $this->driveFiles[$path];
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2013-07-13 15:02:07 +00:00
|
|
|
/**
|
2014-01-29 03:57:07 +00:00
|
|
|
* Set the Google_Service_Drive_DriveFile object in the cache
|
2013-07-13 15:02:07 +00:00
|
|
|
* @param string $path
|
2014-01-29 03:57:07 +00:00
|
|
|
* @param Google_Service_Drive_DriveFile|false $file
|
2013-07-13 15:02:07 +00:00
|
|
|
*/
|
|
|
|
private function setDriveFile($path, $file) {
|
2013-07-23 14:50:14 +00:00
|
|
|
$path = trim($path, '/');
|
2013-07-13 15:02:07 +00:00
|
|
|
$this->driveFiles[$path] = $file;
|
|
|
|
if ($file === false) {
|
|
|
|
// Set all child paths as false
|
|
|
|
$len = strlen($path);
|
|
|
|
foreach ($this->driveFiles as $key => $file) {
|
|
|
|
if (substr($key, 0, $len) === $path) {
|
|
|
|
$this->driveFiles[$key] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-17 00:09:32 +00: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 15:02:07 +00:00
|
|
|
\OCP\Util::INFO
|
|
|
|
);
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2013-05-17 00:09:32 +00: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-11 21:06:57 +00:00
|
|
|
}
|
2012-02-29 00:15:49 +00:00
|
|
|
|
2012-02-27 19:59:41 +00:00
|
|
|
public function mkdir($path) {
|
2013-07-13 15:02:07 +00:00
|
|
|
if (!$this->is_dir($path)) {
|
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
2014-01-29 03:57:07 +00:00
|
|
|
$folder = new \Google_Service_Drive_DriveFile();
|
2013-07-13 15:02:07 +00:00
|
|
|
$folder->setTitle(basename($path));
|
|
|
|
$folder->setMimeType(self::FOLDER);
|
2014-01-29 03:57:07 +00:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 15:02:07 +00: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 19:59:41 +00:00
|
|
|
}
|
2013-07-13 15:02:07 +00:00
|
|
|
return false;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2012-02-18 02:56:20 +00:00
|
|
|
|
2012-02-27 19:59:41 +00:00
|
|
|
public function rmdir($path) {
|
2014-08-25 12:06:48 +00:00
|
|
|
if (!$this->isDeletable($path)) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-07-23 14:50:14 +00:00
|
|
|
if (trim($path, '/') === '') {
|
2013-07-13 15:02:07 +00:00
|
|
|
$dir = $this->opendir($path);
|
2013-09-04 11:06:04 +00: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 15:02:07 +00:00
|
|
|
}
|
|
|
|
}
|
2013-09-04 11:06:04 +00:00
|
|
|
closedir($dir);
|
2013-07-13 15:02:07 +00:00
|
|
|
}
|
|
|
|
$this->driveFiles = array();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return $this->unlink($path);
|
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function opendir($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
// Remove leading and trailing slashes
|
|
|
|
$path = trim($path, '/');
|
|
|
|
$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 00:15:49 +00:00
|
|
|
}
|
2015-03-31 13:30:49 +00:00
|
|
|
$params['q'] = "'" . str_replace("'","\\'", $folder->getId()) . "' in parents and trashed = false";
|
2013-05-17 00:09:32 +00: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
|
2013-07-13 15:02:07 +00:00
|
|
|
if ($child->getFileExtension() === ''
|
2013-05-17 00:09:32 +00:00
|
|
|
&& $child->getMimeType() !== self::FOLDER
|
|
|
|
) {
|
|
|
|
$name .= '.'.$this->getGoogleDocExtension($child->getMimeType());
|
|
|
|
}
|
|
|
|
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 15:02:07 +00: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 00:09:32 +00:00
|
|
|
} else {
|
2014-01-29 03:57:07 +00:00
|
|
|
// Cache the Google_Service_Drive_DriveFile for future use
|
2013-07-13 15:02:07 +00:00
|
|
|
$this->setDriveFile($filepath, $child);
|
2013-05-17 00:09:32 +00:00
|
|
|
$files[] = $name;
|
2012-03-24 18:49:44 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
$pageToken = $children->getNextPageToken();
|
|
|
|
}
|
|
|
|
\OC\Files\Stream\Dir::register('google'.$path, $files);
|
|
|
|
return opendir('fakedir://google'.$path);
|
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2012-02-29 00:15:49 +00:00
|
|
|
public function stat($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
$stat = array();
|
|
|
|
if ($this->filetype($path) === 'dir') {
|
|
|
|
$stat['size'] = 0;
|
|
|
|
} else {
|
2013-05-17 15:42:14 +00:00
|
|
|
// Check if this is a Google Doc
|
|
|
|
if ($this->getMimeType($path) !== $file->getMimeType()) {
|
|
|
|
// Return unknown file size
|
2014-08-19 12:05:08 +00:00
|
|
|
$stat['size'] = \OCP\Files\FileInfo::SPACE_UNKNOWN;
|
2013-05-17 15:42:14 +00:00
|
|
|
} else {
|
|
|
|
$stat['size'] = $file->getFileSize();
|
|
|
|
}
|
2012-11-30 15:27:11 +00:00
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
$stat['atime'] = strtotime($file->getLastViewedByMeDate());
|
|
|
|
$stat['mtime'] = strtotime($file->getModifiedDate());
|
|
|
|
$stat['ctime'] = strtotime($file->getCreatedDate());
|
2012-03-24 18:49:44 +00:00
|
|
|
return $stat;
|
2013-05-17 00:09:32 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-03-24 18:49:44 +00:00
|
|
|
}
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function filetype($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
if ($path === '') {
|
2012-02-29 00:15:49 +00:00
|
|
|
return 'dir';
|
2012-11-30 15:27:11 +00:00
|
|
|
} else {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
if ($file->getMimeType() === self::FOLDER) {
|
|
|
|
return 'dir';
|
|
|
|
} else {
|
|
|
|
return 'file';
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-24 21:42:07 +00:00
|
|
|
public function isUpdatable($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
|
|
|
return $file->getEditable();
|
2012-11-30 15:27:11 +00:00
|
|
|
} else {
|
2013-05-17 00:09:32 +00:00
|
|
|
return false;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2012-02-27 19:59:41 +00:00
|
|
|
public function file_exists($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
return (bool)$this->getDriveFile($path);
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2012-02-27 19:59:41 +00:00
|
|
|
public function unlink($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
if ($file) {
|
2013-07-13 15:02:07 +00:00
|
|
|
$result = $this->service->files->trash($file->getId());
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, false);
|
|
|
|
}
|
|
|
|
return (bool)$result;
|
2013-05-17 00:09:32 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-29 00:15:49 +00:00
|
|
|
public function rename($path1, $path2) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path1);
|
|
|
|
if ($file) {
|
|
|
|
if (dirname($path1) === dirname($path2)) {
|
|
|
|
$file->setTitle(basename(($path2)));
|
2012-05-02 18:38:22 +00:00
|
|
|
} else {
|
2013-05-17 00:09:32 +00:00
|
|
|
// Change file parent
|
|
|
|
$parentFolder2 = $this->getDriveFile(dirname($path2));
|
|
|
|
if ($parentFolder2) {
|
2014-01-29 03:57:07 +00:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-05-17 00:09:32 +00:00
|
|
|
$parent->setId($parentFolder2->getId());
|
|
|
|
$file->setParents(array($parent));
|
2013-07-13 15:02:07 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 00:15:49 +00: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-10 21:32:20 +00: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 15:02:07 +00:00
|
|
|
$result = $this->service->files->patch($file->getId(), $file);
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path1, false);
|
|
|
|
$this->setDriveFile($path2, $result);
|
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-10 21:32:20 +00:00
|
|
|
if ($oldfile) {
|
|
|
|
$this->service->files->delete($oldfile->getId());
|
|
|
|
}
|
2013-07-13 15:02:07 +00:00
|
|
|
}
|
|
|
|
return (bool)$result;
|
2013-05-17 00:09:32 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2012-02-29 00:15:49 +00:00
|
|
|
public function fopen($path, $mode) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$pos = strrpos($path, '.');
|
|
|
|
if ($pos !== false) {
|
|
|
|
$ext = substr($path, $pos);
|
|
|
|
} else {
|
|
|
|
$ext = '';
|
|
|
|
}
|
2012-05-02 18:38:22 +00:00
|
|
|
switch ($mode) {
|
|
|
|
case 'r':
|
|
|
|
case 'rb':
|
2013-05-17 00:09:32 +00: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 03:57:07 +00:00
|
|
|
$request = new \Google_Http_Request($downloadUrl, 'GET', null, null);
|
|
|
|
$httpRequest = $this->client->getAuth()->authenticatedRequest($request);
|
2013-05-17 00:09:32 +00:00
|
|
|
if ($httpRequest->getResponseHttpCode() == 200) {
|
|
|
|
$tmpFile = \OC_Helper::tmpFile($ext);
|
|
|
|
$data = $httpRequest->getResponseBody();
|
|
|
|
file_put_contents($tmpFile, $data);
|
|
|
|
return fopen($tmpFile, $mode);
|
|
|
|
}
|
|
|
|
}
|
2012-05-02 18:38:22 +00:00
|
|
|
}
|
2013-07-13 15:02:07 +00:00
|
|
|
return false;
|
2012-05-02 18:38:22 +00: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+':
|
2012-09-07 16:30:48 +00:00
|
|
|
$tmpFile = \OC_Helper::tmpFile($ext);
|
2013-01-28 14:34:15 +00:00
|
|
|
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
|
2012-05-02 18:38:22 +00:00
|
|
|
if ($this->file_exists($path)) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$source = $this->fopen($path, 'rb');
|
2012-05-02 18:38:22 +00:00
|
|
|
file_put_contents($tmpFile, $source);
|
|
|
|
}
|
|
|
|
self::$tempFiles[$tmpFile] = $path;
|
|
|
|
return fopen('close://'.$tmpFile, $mode);
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
|
|
|
|
2012-05-02 18:38:22 +00:00
|
|
|
public function writeBack($tmpFile) {
|
|
|
|
if (isset(self::$tempFiles[$tmpFile])) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$path = self::$tempFiles[$tmpFile];
|
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
|
|
|
// TODO Research resumable upload
|
2013-07-13 15:02:07 +00:00
|
|
|
$mimetype = \OC_Helper::getMimeType($tmpFile);
|
2013-05-17 00:09:32 +00:00
|
|
|
$data = file_get_contents($tmpFile);
|
|
|
|
$params = array(
|
|
|
|
'data' => $data,
|
|
|
|
'mimeType' => $mimetype,
|
2014-01-29 03:57:07 +00:00
|
|
|
'uploadType' => 'media'
|
2013-05-17 00:09:32 +00:00
|
|
|
);
|
2013-07-13 15:02:07 +00:00
|
|
|
$result = false;
|
2013-05-17 00:09:32 +00:00
|
|
|
if ($this->file_exists($path)) {
|
2013-07-13 15:02:07 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
|
|
|
$result = $this->service->files->update($file->getId(), $file, $params);
|
2012-05-02 18:38:22 +00:00
|
|
|
} else {
|
2014-01-29 03:57:07 +00:00
|
|
|
$file = new \Google_Service_Drive_DriveFile();
|
2013-07-13 15:02:07 +00:00
|
|
|
$file->setTitle(basename($path));
|
|
|
|
$file->setMimeType($mimetype);
|
2014-01-29 03:57:07 +00:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 15:02:07 +00:00
|
|
|
$parent->setId($parentFolder->getId());
|
|
|
|
$file->setParents(array($parent));
|
|
|
|
$result = $this->service->files->insert($file, $params);
|
|
|
|
}
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, $result);
|
2012-05-02 18:38:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
unlink($tmpFile);
|
2012-05-02 18:38:22 +00:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2013-05-17 00:09:32 +00: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 00:15:49 +00:00
|
|
|
return 'httpd/unix-directory';
|
2013-05-17 00:09:32 +00: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 00:15:49 +00:00
|
|
|
} else {
|
2013-05-17 00:09:32 +00:00
|
|
|
return $mimetype;
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
2013-05-17 00:09:32 +00:00
|
|
|
} else {
|
|
|
|
return false;
|
2012-02-29 00:15:49 +00:00
|
|
|
}
|
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2012-02-27 19:59:41 +00:00
|
|
|
public function free_space($path) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$about = $this->service->about->get();
|
|
|
|
return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2012-08-29 06:42:49 +00:00
|
|
|
|
2012-03-24 18:49:44 +00:00
|
|
|
public function touch($path, $mtime = null) {
|
2013-05-17 00:09:32 +00:00
|
|
|
$file = $this->getDriveFile($path);
|
2013-07-13 15:02:07 +00:00
|
|
|
$result = false;
|
2013-05-17 00:09:32 +00:00
|
|
|
if ($file) {
|
|
|
|
if (isset($mtime)) {
|
2014-11-08 08:38:00 +00: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 15:02:07 +00:00
|
|
|
$result = $this->service->files->patch($file->getId(), $file, array(
|
2013-05-17 00:09:32 +00:00
|
|
|
'setModifiedDate' => true,
|
|
|
|
));
|
|
|
|
} else {
|
2013-07-13 15:02:07 +00:00
|
|
|
$result = $this->service->files->touch($file->getId());
|
2013-05-17 00:09:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2013-07-13 15:02:07 +00:00
|
|
|
$parentFolder = $this->getDriveFile(dirname($path));
|
|
|
|
if ($parentFolder) {
|
2014-01-29 03:57:07 +00:00
|
|
|
$file = new \Google_Service_Drive_DriveFile();
|
2013-07-13 15:02:07 +00:00
|
|
|
$file->setTitle(basename($path));
|
2014-01-29 03:57:07 +00:00
|
|
|
$parent = new \Google_Service_Drive_ParentReference();
|
2013-07-13 15:02:07 +00:00
|
|
|
$parent->setId($parentFolder->getId());
|
|
|
|
$file->setParents(array($parent));
|
|
|
|
$result = $this->service->files->insert($file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($result) {
|
|
|
|
$this->setDriveFile($path, $result);
|
2013-05-17 00:09:32 +00:00
|
|
|
}
|
2013-07-13 15:02:07 +00:00
|
|
|
return (bool)$result;
|
2012-02-27 19:59:41 +00:00
|
|
|
}
|
2012-02-29 00:15:49 +00:00
|
|
|
|
2012-12-28 17:00:48 +00:00
|
|
|
public function test() {
|
|
|
|
if ($this->free_space('')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-05-31 01:45:16 +00:00
|
|
|
public function hasUpdated($path, $time) {
|
2014-02-13 15:28:49 +00:00
|
|
|
$appConfig = \OC::$server->getAppConfig();
|
2013-05-31 01:45:16 +00: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 15:28:49 +00:00
|
|
|
$startChangeId = $appConfig->getValue('files_external', $this->getId().'cId');
|
2013-05-31 01:45:16 +00: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 15:02:07 +00: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 01:45:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$pageToken = $changes->getNextPageToken();
|
|
|
|
} else {
|
|
|
|
// Assuming the initial scan just occurred and changes are negligible
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-02-13 15:28:49 +00:00
|
|
|
$appConfig->setValue('files_external', $this->getId().'cId', $largestChangeId);
|
2013-05-31 01:45:16 +00:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-02 13:44:56 +00:00
|
|
|
/**
|
|
|
|
* check if curl is installed
|
|
|
|
*/
|
|
|
|
public static function checkDependencies() {
|
2015-03-12 20:43:41 +00:00
|
|
|
return true;
|
2013-08-02 13:44:56 +00:00
|
|
|
}
|
|
|
|
|
2013-08-18 09:02:08 +00:00
|
|
|
}
|