Switch to using Google Drive SDK, closes #2047

This commit is contained in:
Michael Gapczynski 2013-05-16 20:09:32 -04:00
parent 1ec92b6377
commit d8c660c6d5
4 changed files with 457 additions and 593 deletions

View File

@ -1,64 +1,41 @@
<?php
require_once 'Google/common.inc.php';
require_once 'google-api-php-client/src/Google_Client.php';
OCP\JSON::checkAppEnabled('files_external');
OCP\JSON::checkLoggedIn();
OCP\JSON::callCheck();
$consumer = new OAuthConsumer('anonymous', 'anonymous');
$sigMethod = new OAuthSignatureMethod_HMAC_SHA1();
if (isset($_POST['step'])) {
switch ($_POST['step']) {
case 1:
if (isset($_POST['callback'])) {
$callback = $_POST['callback'];
} else {
$callback = null;
}
$scope = 'https://docs.google.com/feeds/'
.' https://docs.googleusercontent.com/'
.' https://spreadsheets.google.com/feeds/';
$url = 'https://www.google.com/accounts/OAuthGetRequestToken?scope='.urlencode($scope);
$params = array('scope' => $scope, 'oauth_callback' => $callback);
$request = OAuthRequest::from_consumer_and_token($consumer, null, 'GET', $url, $params);
$request->sign_request($sigMethod, $consumer, null);
$response = send_signed_request('GET', $url, array($request->to_header()), null, false);
$token = array();
parse_str($response, $token);
if (isset($token['oauth_token']) && isset($token['oauth_token_secret'])) {
$authUrl = 'https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token='.$token['oauth_token'];
OCP\JSON::success(array('data' => array('url' => $authUrl,
'request_token' => $token['oauth_token'],
'request_token_secret' => $token['oauth_token_secret'])));
} else {
if (isset($_POST['client_id']) && isset($_POST['client_secret']) && isset($_POST['redirect'])) {
$client = new Google_Client();
$client->setClientId($_POST['client_id']);
$client->setClientSecret($_POST['client_secret']);
$client->setRedirectUri($_POST['redirect']);
$client->setScopes(array('https://www.googleapis.com/auth/drive'));
if (isset($_POST['step'])) {
$step = $_POST['step'];
if ($step == 1) {
try {
$authUrl = $client->createAuthUrl();
OCP\JSON::success(array('data' => array(
'url' => $authUrl
)));
} catch (Exception $exception) {
OCP\JSON::error(array('data' => array(
'message' => 'Fetching request tokens failed. Error: '.$response
)));
'message' => 'Step 1 failed. Exception: '.$exception->getMessage()
)));
}
break;
case 2:
if (isset($_POST['oauth_verifier'])
&& isset($_POST['request_token'])
&& isset($_POST['request_token_secret'])
) {
$token = new OAuthToken($_POST['request_token'], $_POST['request_token_secret']);
$url = 'https://www.google.com/accounts/OAuthGetAccessToken';
$request = OAuthRequest::from_consumer_and_token($consumer, $token, 'GET', $url,
array('oauth_verifier' => $_POST['oauth_verifier']));
$request->sign_request($sigMethod, $consumer, $token);
$response = send_signed_request('GET', $url, array($request->to_header()), null, false);
$token = array();
parse_str($response, $token);
if (isset($token['oauth_token']) && isset($token['oauth_token_secret'])) {
OCP\JSON::success(array('access_token' => $token['oauth_token'],
'access_token_secret' => $token['oauth_token_secret']));
} else {
OCP\JSON::error(array('data' => array(
'message' => 'Fetching access tokens failed. Error: '.$response
)));
}
} else if ($step == 2 && isset($_POST['code'])) {
try {
$token = $client->authenticate($_POST['code']);
OCP\JSON::success(array('data' => array(
'token' => $token
)));
} catch (Exception $exception) {
OCP\JSON::error(array('data' => array(
'message' => 'Step 2 failed. Exception: '.$exception->getMessage()
)));
}
break;
}
}
}
}

View File

@ -1,69 +1,89 @@
$(document).ready(function() {
$('#externalStorage tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google').each(function(index, tr) {
setupGoogleRow(tr);
});
$('#externalStorage').on('change', '#selectBackend', function() {
if ($(this).val() == '\\OC\\Files\\Storage\\Google') {
setupGoogleRow($('#externalStorage tbody>tr:last').prev('tr'));
}
});
function setupGoogleRow(tr) {
var configured = $(tr).find('[data-parameter="configured"]');
$('#externalStorage tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google').each(function() {
var configured = $(this).find('[data-parameter="configured"]');
if ($(configured).val() == 'true') {
$(tr).find('.configuration').append('<span id="access" style="padding-left:0.5em;">'+t('files_external', 'Access granted')+'</span>');
$(this).find('.configuration input').attr('disabled', 'disabled');
$(this).find('.configuration').append($('<span/>').attr('id', 'access')
.text(t('files_external', 'Access granted')));
} else {
var token = $(tr).find('[data-parameter="token"]');
var token_secret = $(tr).find('[data-parameter="token_secret"]');
var params = {};
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
params[key] = value;
});
if (params['oauth_token'] !== undefined && params['oauth_verifier'] !== undefined && decodeURIComponent(params['oauth_token']) == $(token).val()) {
var statusSpan = $(tr).find('.status span');
statusSpan.removeClass();
statusSpan.addClass('waiting');
$.post(OC.filePath('files_external', 'ajax', 'google.php'), { step: 2, oauth_verifier: params['oauth_verifier'], request_token: $(token).val(), request_token_secret: $(token_secret).val() }, function(result) {
if (result && result.status == 'success') {
$(token).val(result.access_token);
$(token_secret).val(result.access_token_secret);
$(configured).val('true');
OC.MountConfig.saveStorage(tr);
$(tr).find('.configuration').append('<span id="access" style="padding-left:0.5em;">'+t('files_external', 'Access granted')+'</span>');
} else {
OC.dialogs.alert(result.data.message, t('files_external', 'Error configuring Google Drive storage'));
onGoogleInputsChange(tr);
}
var client_id = $(this).find('.configuration [data-parameter="client_id"]').val();
var client_secret = $(this).find('.configuration [data-parameter="client_secret"]')
.val();
if (client_id != '' && client_secret != '') {
var params = {};
window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
params[key] = value;
});
if (params['code'] !== undefined) {
var tr = $(this);
var token = $(this).find('.configuration [data-parameter="token"]');
var statusSpan = $(tr).find('.status span');
statusSpan.removeClass();
statusSpan.addClass('waiting');
$.post(OC.filePath('files_external', 'ajax', 'google.php'),
{
step: 2,
client_id: client_id,
client_secret: client_secret,
redirect: location.protocol + '//' + location.host + location.pathname,
code: params['code'],
}, function(result) {
if (result && result.status == 'success') {
$(token).val(result.data.token);
$(configured).val('true');
OC.MountConfig.saveStorage(tr);
$(tr).find('.configuration input').attr('disabled', 'disabled');
$(tr).find('.configuration').append($('<span/>')
.attr('id', 'access')
.text(t('files_external', 'Access granted')));
} else {
OC.dialogs.alert(result.data.message,
t('files_external', 'Error configuring Google Drive storage')
);
}
}
);
}
} else {
onGoogleInputsChange(tr);
onGoogleInputsChange($(this));
}
}
}
$('#externalStorage').on('paste', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google td', function() {
var tr = $(this).parent();
setTimeout(function() {
onGoogleInputsChange(tr);
}, 20);
});
$('#externalStorage').on('keyup', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google td', function() {
onGoogleInputsChange($(this).parent());
});
$('#externalStorage').on('paste', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google td',
function() {
var tr = $(this).parent();
setTimeout(function() {
onGoogleInputsChange(tr);
}, 20);
}
);
$('#externalStorage').on('change', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google .chzn-select', function() {
onGoogleInputsChange($(this).parent().parent());
});
$('#externalStorage').on('keyup', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google td',
function() {
onGoogleInputsChange($(this).parent());
}
);
$('#externalStorage').on('change', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\Google .chzn-select'
, function() {
onGoogleInputsChange($(this).parent().parent());
}
);
function onGoogleInputsChange(tr) {
if ($(tr).find('[data-parameter="configured"]').val() != 'true') {
var config = $(tr).find('.configuration');
if ($(tr).find('.mountPoint input').val() != '' && ($(tr).find('.chzn-select').length == 0 || $(tr).find('.chzn-select').val() != null)) {
if ($(tr).find('.mountPoint input').val() != ''
&& $(config).find('[data-parameter="client_id"]').val() != ''
&& $(config).find('[data-parameter="client_secret"]').val() != ''
&& ($(tr).find('.chzn-select').length == 0
|| $(tr).find('.chzn-select').val() != null))
{
if ($(tr).find('.google').length == 0) {
$(config).append('<a class="button google">'+t('files_external', 'Grant access')+'</a>');
$(config).append($('<a/>').addClass('button google')
.text(t('files_external', 'Grant access')));
} else {
$(tr).find('.google').show();
}
@ -77,22 +97,33 @@ $(document).ready(function() {
event.preventDefault();
var tr = $(this).parent().parent();
var configured = $(this).parent().find('[data-parameter="configured"]');
var token = $(this).parent().find('[data-parameter="token"]');
var token_secret = $(this).parent().find('[data-parameter="token_secret"]');
var client_id = $(this).parent().find('[data-parameter="client_id"]').val();
var client_secret = $(this).parent().find('[data-parameter="client_secret"]').val();
var statusSpan = $(tr).find('.status span');
$.post(OC.filePath('files_external', 'ajax', 'google.php'), { step: 1, callback: location.protocol + '//' + location.host + location.pathname }, function(result) {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val(result.data.request_token);
$(token_secret).val(result.data.request_token_secret);
OC.MountConfig.saveStorage(tr);
statusSpan.removeClass();
statusSpan.addClass('waiting');
window.location = result.data.url;
} else {
OC.dialogs.alert(result.data.message, t('files_external', 'Error configuring Google Drive storage'));
}
});
if (client_id != '' && client_secret != '') {
var token = $(this).parent().find('[data-parameter="token"]');
$.post(OC.filePath('files_external', 'ajax', 'google.php'),
{
step: 1,
client_id: client_id,
client_secret: client_secret,
redirect: location.protocol + '//' + location.host + location.pathname,
}, function(result) {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val('false');
OC.MountConfig.saveStorage(tr);
statusSpan.removeClass();
statusSpan.addClass('waiting');
window.location = result.data.url;
} else {
OC.dialogs.alert(result.data.message,
t('files_external', 'Error configuring Google Drive storage')
);
}
}
);
}
});
});
});

View File

@ -74,8 +74,9 @@ class OC_Mount_Config {
'backend' => 'Google Drive',
'configuration' => array(
'configured' => '#configured',
'token' => '#token',
'token_secret' => '#token secret'),
'client_id' => 'Client ID',
'client_secret' => 'Client secret',
'token' => '#token'),
'custom' => 'google');
$backends['\OC\Files\Storage\SWIFT']=array(

View File

@ -1,5 +1,4 @@
<?php
/**
* ownCloud
*
@ -22,213 +21,161 @@
namespace OC\Files\Storage;
require_once 'Google/common.inc.php';
require_once 'google-api-php-client/src/Google_Client.php';
require_once 'google-api-php-client/src/contrib/Google_DriveService.php';
class Google extends \OC\Files\Storage\Common {
private $consumer;
private $oauth_token;
private $sig_method;
private $entries;
private $id;
private $service;
private $driveFiles;
private static $tempFiles = array();
// 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';
public function __construct($params) {
if (isset($params['configured']) && $params['configured'] == 'true'
if (isset($params['configured']) && $params['configured'] === 'true'
&& isset($params['client_id']) && isset($params['client_secret'])
&& isset($params['token'])
&& isset($params['token_secret'])
) {
$consumer_key = isset($params['consumer_key']) ? $params['consumer_key'] : 'anonymous';
$consumer_secret = isset($params['consumer_secret']) ? $params['consumer_secret'] : 'anonymous';
$this->id = 'google::' . $params['token'];
$this->consumer = new \OAuthConsumer($consumer_key, $consumer_secret);
$this->oauth_token = new \OAuthToken($params['token'], $params['token_secret']);
$this->sig_method = new \OAuthSignatureMethod_HMAC_SHA1();
$this->entries = array();
$client = new \Google_Client();
$client->setClientId($params['client_id']);
$client->setClientSecret($params['client_secret']);
$client->setRedirectUri('http://localhost/workspace/core');
$client->setScopes(array('https://www.googleapis.com/auth/drive'));
$client->setUseObjects(true);
$client->setAccessToken($params['token']);
$this->service = new \Google_DriveService($client);
$this->root = isset($params['root']) ? $params['root'] : '';
$token = json_decode($params['token'], true);
$this->id = 'google::'.$params['client_id'].$token['created'];
} else {
throw new \Exception('Creating \OC\Files\Storage\Google storage failed');
}
}
private function sendRequest($uri,
$httpMethod,
$postData = null,
$extraHeaders = null,
$isDownload = false,
$returnHeaders = false,
$isContentXML = true,
$returnHTTPCode = false) {
$uri = trim($uri);
// create an associative array from each key/value url query param pair.
$params = array();
$pieces = explode('?', $uri);
if (isset($pieces[1])) {
$params = explode_assoc('=', '&', $pieces[1]);
}
// urlencode each url parameter key/value pair
$tempStr = $pieces[0];
foreach ($params as $key => $value) {
$tempStr .= '&' . urlencode($key) . '=' . urlencode($value);
}
$uri = preg_replace('/&/', '?', $tempStr, 1);
$request = \OAuthRequest::from_consumer_and_token($this->consumer,
$this->oauth_token,
$httpMethod,
$uri,
$params);
$request->sign_request($this->sig_method, $this->consumer, $this->oauth_token);
$auth_header = $request->to_header();
$headers = array($auth_header, 'GData-Version: 3.0');
if ($isContentXML) {
$headers = array_merge($headers, array('Content-Type: application/atom+xml'));
}
if (is_array($extraHeaders)) {
$headers = array_merge($headers, $extraHeaders);
}
$curl = curl_init($uri);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
switch ($httpMethod) {
case 'GET':
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
break;
case 'POST':
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
break;
case 'PUT':
$headers[] = 'If-Match: *';
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpMethod);
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
break;
case 'DELETE':
$headers[] = 'If-Match: *';
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpMethod);
break;
default:
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
if ($isDownload) {
$tmpFile = \OC_Helper::tmpFile();
$handle = fopen($tmpFile, 'w');
curl_setopt($curl, CURLOPT_FILE, $handle);
}
if ($returnHeaders) {
curl_setopt($curl, CURLOPT_HEADER, true);
}
$result = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($result) {
// TODO https://developers.google.com/google-apps/documents-list/#handling_api_errors
// TODO Log error messages
if ($httpCode <= 308) {
if ($isDownload) {
return $tmpFile;
} else if ($returnHTTPCode) {
return array('result' => $result, 'code' => $httpCode);
} else {
return $result;
}
}
}
return false;
}
private function getFeed($feedUri, $httpMethod, $postData = null) {
$result = $this->sendRequest($feedUri, $httpMethod, $postData);
if ($result) {
$dom = new \DOMDocument();
$dom->loadXML($result);
return $dom;
}
return false;
}
/**
* Base url for google docs feeds
*/
const BASE_URI='https://docs.google.com/feeds';
private function getResource($path) {
$file = basename($path);
if (array_key_exists($file, $this->entries)) {
return $this->entries[$file];
} else {
// Strip the file extension; file could be a native Google Docs resource
if ($pos = strpos($file, '.')) {
$title = substr($file, 0, $pos);
$dom = $this->getFeed(self::BASE_URI.'/default/private/full?showfolders=true&title='.$title, 'GET');
// Check if request was successful and entry exists
if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) {
$this->entries[$file] = $entry;
return $entry;
}
}
$dom = $this->getFeed(self::BASE_URI.'/default/private/full?showfolders=true&title='.$file, 'GET');
// Check if request was successful and entry exists
if ($dom && $entry = $dom->getElementsByTagName('entry')->item(0)) {
$this->entries[$file] = $entry;
return $entry;
}
return false;
}
}
private function getExtension($entry) {
$mimetype = $this->getMimeType('', $entry);
switch ($mimetype) {
case 'httpd/unix-directory':
return '';
case 'application/vnd.oasis.opendocument.text':
return 'odt';
case 'application/vnd.oasis.opendocument.spreadsheet':
return 'ods';
case 'application/vnd.oasis.opendocument.presentation':
return 'pptx';
case 'text/html':
return 'html';
default:
return 'html';
}
}
public function getId(){
public function getId() {
return $this->id;
}
public function mkdir($path) {
$collection = dirname($path);
// Check if path parent is root directory
if ($collection == '/' || $collection == '\.' || $collection == '.') {
$uri = self::BASE_URI.'/default/private/full';
/**
* Get the Google_DriveFile object for the specified path
* @param string $path
* @return Google_DriveFile
*/
private function getDriveFile($path) {
// Remove leading and trailing slashes
$path = trim($this->root.$path, '/');
if (isset($this->driveFiles[$path])) {
return $this->driveFiles[$path];
} else if ($path === '') {
$root = $this->service->files->get('root');
$this->driveFiles[$path] = $root;
return $root;
} else {
// Get parent content link
$dom = $this->getResource(basename($collection));
if ($dom) {
$uri = $dom->getElementsByTagName('content')->item(0)->getAttribute('src');
// 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 {
$q = "title='".$name."' and '".$parentId."' in parents";
$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) {
// Switch cached Google_DriveFile to the correct index
unset($this->driveFiles[$pathWithoutExt]);
$this->driveFiles[$path] = $file;
$parentId = $file->getId();
} else {
return false;
}
} else {
return false;
}
}
}
}
return $this->driveFiles[$path];
}
if (isset($uri)) {
$title = basename($path);
// Construct post data
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom">';
$postData .= '<category scheme="http://schemas.google.com/g/2005#kind"';
$postData .= ' term="http://schemas.google.com/docs/2007#folder"/>';
$postData .= '<title>'.$title.'</title>';
$postData .= '</entry>';
$dom = $this->sendRequest($uri, 'POST', $postData);
if ($dom) {
return true;
}
}
/**
* 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,
\OCP\Util::INFO);
}
/**
* 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 '';
}
}
public function mkdir($path) {
$parentFolder = $this->getDriveFile(dirname($path));
if ($parentFolder) {
$folder = new \Google_DriveFile();
$folder->setTitle(basename($path));
$folder->setMimeType(self::FOLDER);
$parent = new \Google_ParentReference();
$parent->setId($parentFolder->getId());
$folder->setParents(array($parent));
return (bool)$this->service->files->insert($folder);
} else {
return false;
}
return false;
}
public function rmdir($path) {
@ -236,92 +183,98 @@ class Google extends \OC\Files\Storage\Common {
}
public function opendir($path) {
if ($path == '' || $path == '/') {
$next = self::BASE_URI.'/default/private/full/folder%3Aroot/contents';
// 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;
}
$params['q'] = "'".$folder->getId()."' in parents";
$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
if ($child->getFileExtension() == ''
&& $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
if (isset($this->driveFiles[$filepath]) && !isset($duplicates[$filepath])) {
// Save this key to unset later in case there are more than 2 duplicates
$duplicates[$filepath] = $name;
} else {
// Cache the Google_DriveFile for future use
$this->driveFiles[$filepath] = $child;
$files[] = $name;
}
}
$pageToken = $children->getNextPageToken();
}
// Remove all duplicate files
foreach ($duplicates as $filepath => $name) {
unset($this->driveFiles[$filepath]);
$key = array_search($name, $files);
unset($files[$key]);
$this->onDuplicateFileDetected($filepath);
}
// Reindex $files array if duplicates were removed
// This is necessary for \OC\Files\Stream\Dir
if (!empty($duplicates)) {
$files = array_values($files);
}
\OC\Files\Stream\Dir::register('google'.$path, $files);
return opendir('fakedir://google'.$path);
} else {
$entry = $this->getResource($path);
if ($entry) {
$next = $entry->getElementsByTagName('content')->item(0)->getAttribute('src');
return false;
}
}
public function stat($path) {
$file = $this->getDriveFile($path);
if ($file) {
$stat = array();
if ($this->filetype($path) === 'dir') {
$stat['size'] = 0;
} else {
$stat['size'] = $file->getFileSize();
}
$stat['atime'] = strtotime($file->getLastViewedByMeDate());
$stat['mtime'] = strtotime($file->getModifiedDate());
$stat['ctime'] = strtotime($file->getCreatedDate());
return $stat;
} else {
return false;
}
}
public function filetype($path) {
if ($path === '') {
return 'dir';
} else {
$file = $this->getDriveFile($path);
if ($file) {
if ($file->getMimeType() === self::FOLDER) {
return 'dir';
} else {
return 'file';
}
} else {
return false;
}
}
$files = array();
while ($next) {
$dom = $this->getFeed($next, 'GET');
$links = $dom->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'next') {
$next = $link->getAttribute('src');
break;
} else {
$next = false;
}
}
$entries = $dom->getElementsByTagName('entry');
foreach ($entries as $entry) {
$name = $entry->getElementsByTagName('title')->item(0)->nodeValue;
// Google Docs resources don't always include extensions in title
if ( ! strpos($name, '.')) {
$extension = $this->getExtension($entry);
if ($extension != '') {
$name .= '.'.$extension;
}
}
$files[] = basename($name);
// Cache entry for future use
$this->entries[$name] = $entry;
}
}
\OC\Files\Stream\Dir::register('google'.$path, $files);
return opendir('fakedir://google'.$path);
}
public function stat($path) {
if ($path == '' || $path == '/') {
$stat['size'] = $this->free_space($path);
$stat['atime'] = time();
$stat['mtime'] = time();
$stat['ctime'] = time();
} else {
$entry = $this->getResource($path);
if ($entry) {
// NOTE: Native resources don't have a file size
$stat['size'] = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005',
'quotaBytesUsed')->item(0)->nodeValue;
//if (isset($atime = $entry->getElementsByTagNameNS('http://schemas.google.com/g/2005',
// 'lastViewed')->item(0)->nodeValue))
//$stat['atime'] = strtotime($entry->getElementsByTagNameNS('http://schemas.google.com/g/2005',
// 'lastViewed')->item(0)->nodeValue);
$stat['mtime'] = strtotime($entry->getElementsByTagName('updated')->item(0)->nodeValue);
}
}
if (isset($stat)) {
return $stat;
}
return false;
}
public function filetype($path) {
if ($path == '' || $path == '/') {
return 'dir';
} else {
$entry = $this->getResource($path);
if ($entry) {
$categories = $entry->getElementsByTagName('category');
foreach ($categories as $category) {
if ($category->getAttribute('scheme') == 'http://schemas.google.com/g/2005#kind') {
$type = $category->getAttribute('label');
if (strlen(strstr($type, 'folder')) > 0) {
return 'dir';
} else {
return 'file';
}
}
}
}
}
return false;
}
public function isReadable($path) {
@ -329,109 +282,79 @@ class Google extends \OC\Files\Storage\Common {
}
public function isUpdatable($path) {
if ($path == '' || $path == '/') {
return true;
$file = $this->getDriveFile($path);
if ($file) {
return $file->getEditable();
} else {
$entry = $this->getResource($path);
if ($entry) {
// Check if edit or edit-media links exist
$links = $entry->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'edit') {
return true;
} else if ($link->getAttribute('rel') == 'edit-media') {
return true;
}
}
}
return false;
}
return false;
}
public function file_exists($path) {
if ($path == '' || $path == '/') {
return true;
} else if ($this->getResource($path)) {
return true;
}
return false;
return (bool)$this->getDriveFile($path);
}
public function unlink($path) {
// Get resource self link to trash resource
$entry = $this->getResource($path);
if ($entry) {
$links = $entry->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'self') {
$uri = $link->getAttribute('href');
break;
}
}
$file = $this->getDriveFile($path);
if ($file) {
return (bool)$this->service->files->trash($file->getId());
} else {
return false;
}
if (isset($uri)) {
$this->sendRequest($uri, 'DELETE');
return true;
}
return false;
}
public function rename($path1, $path2) {
$entry = $this->getResource($path1);
if ($entry) {
$collection = dirname($path2);
if (dirname($path1) == $collection) {
// Get resource edit link to rename resource
$etag = $entry->getAttribute('gd:etag');
$links = $entry->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'edit') {
$uri = $link->getAttribute('href');
break;
}
}
$title = basename($path2);
// Construct post data
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom"';
$postData .= ' xmlns:docs="http://schemas.google.com/docs/2007"';
$postData .= ' xmlns:gd="http://schemas.google.com/g/2005"';
$postData .= ' gd:etag='.$etag.'>';
$postData .= '<title>'.$title.'</title>';
$postData .= '</entry>';
$this->sendRequest($uri, 'PUT', $postData);
return true;
$file = $this->getDriveFile($path1);
if ($file) {
if (dirname($path1) === dirname($path2)) {
$file->setTitle(basename(($path2)));
} else {
// Move to different collection
$collectionEntry = $this->getResource($collection);
if ($collectionEntry) {
$feedUri = $collectionEntry->getElementsByTagName('content')->item(0)->getAttribute('src');
// Construct post data
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom">';
$postData .= '<id>'.$entry->getElementsByTagName('id')->item(0).'</id>';
$postData .= '</entry>';
$this->sendRequest($feedUri, 'POST', $postData);
return true;
// Change file parent
$parentFolder2 = $this->getDriveFile(dirname($path2));
if ($parentFolder2) {
$parent = new \Google_ParentReference();
$parent->setId($parentFolder2->getId());
$file->setParents(array($parent));
}
}
return (bool)$this->service->files->patch($file->getId(), $file);
} else {
return false;
}
return false;
}
public function fopen($path, $mode) {
$pos = strrpos($path, '.');
if ($pos !== false) {
$ext = substr($path, $pos);
} else {
$ext = '';
}
switch ($mode) {
case 'r':
case 'rb':
$entry = $this->getResource($path);
if ($entry) {
$extension = $this->getExtension($entry);
$downloadUri = $entry->getElementsByTagName('content')->item(0)->getAttribute('src');
// TODO Non-native documents don't need these additional parameters
$downloadUri .= '&exportFormat='.$extension.'&format='.$extension;
$tmpFile = $this->sendRequest($downloadUri, 'GET', null, null, true);
return fopen($tmpFile, 'r');
$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)) {
$request = new \Google_HttpRequest($downloadUrl, 'GET', null, null);
$httpRequest = \Google_Client::$io->authenticatedRequest($request);
if ($httpRequest->getResponseHttpCode() == 200) {
$tmpFile = \OC_Helper::tmpFile($ext);
$data = $httpRequest->getResponseBody();
file_put_contents($tmpFile, $data);
return fopen($tmpFile, $mode);
}
}
}
return null;
case 'w':
case 'wb':
case 'a':
@ -444,156 +367,88 @@ class Google extends \OC\Files\Storage\Common {
case 'x+':
case 'c':
case 'c+':
if (strrpos($path, '.') !== false) {
$ext = substr($path, strrpos($path, '.'));
} else {
$ext = '';
}
$tmpFile = \OC_Helper::tmpFile($ext);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
if ($this->file_exists($path)) {
$source = $this->fopen($path, 'r');
$source = $this->fopen($path, 'rb');
file_put_contents($tmpFile, $source);
}
self::$tempFiles[$tmpFile] = $path;
return fopen('close://'.$tmpFile, $mode);
}
return false;
}
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$this->uploadFile($tmpFile, self::$tempFiles[$tmpFile]);
$path = self::$tempFiles[$tmpFile];
$parentFolder = $this->getDriveFile(dirname($path));
if ($parentFolder) {
$file = new \Google_DriveFile();
$file->setTitle(basename($path));
$mimetype = \OC_Helper::getMimeType($tmpFile);
$file->setMimeType($mimetype);
$parent = new \Google_ParentReference();
$parent->setId($parentFolder->getId());
$file->setParents(array($parent));
// TODO Research resumable upload
$data = file_get_contents($tmpFile);
$params = array(
'data' => $data,
'mimeType' => $mimetype,
);
if ($this->file_exists($path)) {
$this->service->files->update($file->getId(), $file, $params);
} else {
$this->service->files->insert($file, $params);
}
}
unlink($tmpFile);
}
}
private function uploadFile($path, $target) {
$entry = $this->getResource($target);
if ( ! $entry) {
if (dirname($target) == '.' || dirname($target) == '/') {
$uploadUri = self::BASE_URI.'/upload/create-session/default/private/full/folder%3Aroot/contents';
} else {
$entry = $this->getResource(dirname($target));
}
}
if ( ! isset($uploadUri) && $entry) {
$links = $entry->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'http://schemas.google.com/g/2005#resumable-create-media') {
$uploadUri = $link->getAttribute('href');
break;
}
}
}
if (isset($uploadUri) && $handle = fopen($path, 'r')) {
$uploadUri .= '?convert=false';
$mimetype = \OC_Helper::getMimeType($path);
$size = filesize($path);
$headers = array('X-Upload-Content-Type: ' => $mimetype, 'X-Upload-Content-Length: ' => $size);
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007">';
$postData .= '<title>'.basename($target).'</title>';
$postData .= '</entry>';
$result = $this->sendRequest($uploadUri, 'POST', $postData, $headers, false, true);
if ($result) {
// Get location to upload file
if (preg_match('@^Location: (.*)$@m', $result, $matches)) {
$uploadUri = trim($matches[1]);
}
} else {
return false;
}
// 512 kB chunks
$chunkSize = 524288;
$i = 0;
while (!feof($handle)) {
if ($i + $chunkSize > $size) {
if ($i == 0) {
$chunkSize = $size;
} else {
$chunkSize = $size % $i;
}
}
$end = $i + $chunkSize - 1;
$headers = array('Content-Length: '.$chunkSize,
'Content-Type: '.$mimetype,
'Content-Range: bytes '.$i.'-'.$end.'/'.$size);
$postData = fread($handle, $chunkSize);
$result = $this->sendRequest($uploadUri, 'PUT', $postData, $headers, false, true, false, true);
if ($result['code'] == '308') {
if (preg_match('@^Location: (.*)$@m', $result['result'], $matches)) {
// Get next location to upload file chunk
$uploadUri = trim($matches[1]);
}
$i += $chunkSize;
} else {
return false;
}
}
// TODO Wait for resource entry
}
}
public function getMimeType($path, $entry = null) {
// Entry can be passed, because extension is required for opendir
// and the entry can't be cached without the extension
if ($entry == null) {
if ($path == '' || $path == '/') {
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) {
return 'httpd/unix-directory';
} 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';
} else {
$entry = $this->getResource($path);
return $mimetype;
}
} else {
return false;
}
if ($entry) {
$mimetype = $entry->getElementsByTagName('content')->item(0)->getAttribute('type');
// Native Google Docs resources often default to text/html,
// but it may be more useful to default to a corresponding ODF mimetype
// Collections get reported as application/atom+xml,
// make sure it actually is a folder and fix the mimetype
if ($mimetype == 'text/html' || $mimetype == 'application/atom+xml;type=feed') {
$categories = $entry->getElementsByTagName('category');
foreach ($categories as $category) {
if ($category->getAttribute('scheme') == 'http://schemas.google.com/g/2005#kind') {
$type = $category->getAttribute('label');
if (strlen(strstr($type, 'folder')) > 0) {
return 'httpd/unix-directory';
} else if (strlen(strstr($type, 'document')) > 0) {
return 'application/vnd.oasis.opendocument.text';
} else if (strlen(strstr($type, 'spreadsheet')) > 0) {
return 'application/vnd.oasis.opendocument.spreadsheet';
} else if (strlen(strstr($type, 'presentation')) > 0) {
return 'application/vnd.oasis.opendocument.presentation';
} else if (strlen(strstr($type, 'drawing')) > 0) {
return 'application/vnd.oasis.opendocument.graphics';
} else {
// If nothing matches return text/html,
// all native Google Docs resources can be exported as text/html
return 'text/html';
}
}
}
}
return $mimetype;
}
return false;
}
public function free_space($path) {
$dom = $this->getFeed(self::BASE_URI.'/metadata/default', 'GET');
if ($dom) {
// NOTE: Native Google Docs resources don't count towards quota
$total = $dom->getElementsByTagNameNS('http://schemas.google.com/g/2005',
'quotaBytesTotal')->item(0)->nodeValue;
$used = $dom->getElementsByTagNameNS('http://schemas.google.com/g/2005',
'quotaBytesUsed')->item(0)->nodeValue;
return $total - $used;
}
return false;
$about = $this->service->about->get();
return $about->getQuotaBytesTotal() - $about->getQuotaBytesUsed();
}
public function touch($path, $mtime = null) {
$file = $this->getDriveFile($path);
if ($file) {
if (isset($mtime)) {
$file->setModifiedDate($mtime);
$this->service->files->patch($file->getId(), $file, array(
'setModifiedDate' => true,
));
} else {
return (bool)$this->service->files->touch($file->getId());
}
} else {
return false;
}
}
public function test() {
@ -603,4 +458,4 @@ class Google extends \OC\Files\Storage\Common {
return false;
}
}
}