$(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();
}
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')
+ );
+ }
+ }
+ );
+ }
});
-});
+});
\ No newline at end of file
<?php
-
/**
* ownCloud
*
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;
+ public function getId() {
+ return $this->id;
}
/**
- * Base url for google docs feeds
+ * Get the Google_DriveFile object for the specified path
+ * @param string $path
+ * @return Google_DriveFile
*/
- 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];
+ 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 {
- // 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;
+ // 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;
+ }
+ }
}
}
- $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;
+ return $this->driveFiles[$path];
}
}
- 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';
- }
+ /**
+ * 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);
}
- public function getId(){
- return $this->id;
+ /**
+ * 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) {
- $collection = dirname($path);
- // Check if path parent is root directory
- if ($collection == '/' || $collection == '\.' || $collection == '.') {
- $uri = self::BASE_URI.'/default/private/full';
+ $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 {
- // Get parent content link
- $dom = $this->getResource(basename($collection));
- if ($dom) {
- $uri = $dom->getElementsByTagName('content')->item(0)->getAttribute('src');
- }
- }
- 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;
- }
+ return false;
}
- return false;
}
public function rmdir($path) {
}
public function opendir($path) {
- if ($path == '' || $path == '/') {
- $next = self::BASE_URI.'/default/private/full/folder%3Aroot/contents';
- } else {
- $entry = $this->getResource($path);
- if ($entry) {
- $next = $entry->getElementsByTagName('content')->item(0)->getAttribute('src');
- } 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;
+ // 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;
}
- }
- $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;
+ $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;
}
}
- $files[] = basename($name);
- // Cache entry for future use
- $this->entries[$name] = $entry;
+ $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 {
+ return false;
}
- \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);
+ $file = $this->getDriveFile($path);
+ if ($file) {
+ $stat = array();
+ if ($this->filetype($path) === 'dir') {
+ $stat['size'] = 0;
+ } else {
+ $stat['size'] = $file->getFileSize();
}
- }
- if (isset($stat)) {
+ $stat['atime'] = strtotime($file->getLastViewedByMeDate());
+ $stat['mtime'] = strtotime($file->getModifiedDate());
+ $stat['ctime'] = strtotime($file->getCreatedDate());
return $stat;
+ } else {
+ return false;
}
- return false;
}
public function filetype($path) {
- if ($path == '' || $path == '/') {
+ if ($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';
- }
- }
+ $file = $this->getDriveFile($path);
+ if ($file) {
+ if ($file->getMimeType() === self::FOLDER) {
+ return 'dir';
+ } else {
+ return 'file';
}
+ } else {
+ return false;
}
}
- return false;
}
public function isReadable($path) {
}
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;
- }
- }
- }
- if (isset($uri)) {
- $this->sendRequest($uri, 'DELETE');
- return true;
+ $file = $this->getDriveFile($path);
+ if ($file) {
+ return (bool)$this->service->files->trash($file->getId());
+ } else {
+ return false;
}
- 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':
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]);
- 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;
+ $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 {
- return false;
+ $this->service->files->insert($file, $params);
}
}
- // TODO Wait for resource entry
+ unlink($tmpFile);
}
}
- 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() {
return false;
}
-}
+}
\ No newline at end of file