]> source.dussan.org Git - nextcloud-server.git/commitdiff
Switch to using Google Drive SDK, closes #2047
authorMichael Gapczynski <mtgap@owncloud.com>
Fri, 17 May 2013 00:09:32 +0000 (20:09 -0400)
committerVictor Dubiniuk <victor.dubiniuk@gmail.com>
Sat, 3 Aug 2013 12:37:34 +0000 (15:37 +0300)
apps/files_external/ajax/google.php
apps/files_external/js/google.js
apps/files_external/lib/config.php
apps/files_external/lib/google.php

index 70adcb2c2ad4f7a8d10bab181544bef73deb604c..bd5a6099bb37271adb2e4a419ba1fbb4de4ad415 100644 (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;
+               }
        }
-}
+}
\ No newline at end of file
index 7be1b338e904fb1cba80cea821d28882a465d18b..7e111a95d982a1d320187d1c1a3672d6ab7d0355 100644 (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')
+                                               );
+                                       }
+                               }
+                       );
+               }
        });
 
-});
+});
\ No newline at end of file
index 394a1621116ee3f2f4fe5d88c96e940e02578582..fec5e87125b1a9618de9abc2dd4c50c30e2e1188 100755 (executable)
@@ -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(
index ec7de3f35709dec0e879dbfd42d3e14f957a70d4..259a776142868ac9887855dd41921b5b32dcf339 100644 (file)
@@ -1,5 +1,4 @@
 <?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) {
@@ -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';
-               } 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) {
@@ -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;
-                               }
-                       }
-               }
-               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':
@@ -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]);
-                       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() {
@@ -603,4 +458,4 @@ class Google extends \OC\Files\Storage\Common {
                return false;
        }
 
-}
+}
\ No newline at end of file