Let's assume you're writing a Google App Engine app in Python.  And you want to use the Google File Picker API to select files from the user's Google Drive, and also the Google Drive API to download the selected files. You can write your server-side appengine app in Python, and then use Javascript for the file picker and file download.  It's not super hard, but I couldn't find it completely documented anywhere.  I only found one description of the tough issues .

At a high-level, your architecture is:

  • Server: App Engine app in Python, which includes:

Google API Python client

- your custom code

  • Client: browser app (we like AngularJS), which includes:

- Google File Picker js support

Google Drive js support

- your custom code

App Engine App Changes

1. Start with your standard App Engine app.

2. Download and install the Google API Python client into your app. It supports oauth2.

3. Add oauth2 auth code at the top of your main.py file:

    # Put the scopes you want below, here are the scopes for read-only drive access and getting user's email address
    ALL_SCOPES = ('https://www.googleapis.com/auth/drive.readonly '
                  'https://www.googleapis.com/auth/userinfo.email')

    # CLIENT_SECRETS, name of a file containing the OAuth 2.0 information for this
    # application, including client_id and client_secret, which are found
    # on the API Access tab on the Google APIs
    # Console <http://code.google.com/apis/console>
    CLIENT_SECRETS = os.path.join(os.path.dirname(__file__), 'client_secrets.json')

    # Helpful message to display in the browser if the CLIENT_SECRETS file is missing.
    MISSING_CLIENT_SECRETS_MESSAGE = '''File client_secrets.json is missing.'''

    # Create decorator.
    http = httplib2.Http(memcache)
    decorator = oauth2decorator_from_clientsecrets(
        CLIENT_SECRETS,
        scope=ALL_SCOPES,
        message=MISSING_CLIENT_SECRETS_MESSAGE)

4. Make your main page have security check:

    # Below is the method which handles the home page.
    # Notice it uses the decorator we just defined.
    # If you're authed then it shows you the standard home page,
    # else it shows you an unauth.html page.
    @decorator.oauth_aware
    def get(self):
        if decorator.has_credentials():
            self.response.out.write(template.render('index.html', {}))
        else:
            url = decorator.authorize_url()
            self.response.out.write(template.render('unauth.html',
                                                   {'authorize_url': url}))

So far, we've put security on the app so that only users who have valid Google Accounts and users who have given permission to our app can access the app.

Javascript Changes

On the client side, right before you're about to use one of the Google services, you should make an ajax call to your server, the server code should:

        @decorator.oauth_aware
        def get(self):
            if decorator.has_credentials():
                if decorator.credentials.access_token_expired:
                    http = decorator.http()
                    decorator.credentials.refresh(http)
                self.response.out.write(jsonDumps(dict(
                    access_token = decorator.credentials.access_token,
                    error = '',
                    expires_in = '10000',  # string duration in seconds. Value doesn't matter in this context.
                    state = decorator._scope,
                )));
            else:
                self.response.out.write(jsonDumps(dict(
                    access_token = '',
                    error = 'User is not logged in or authenticated',
                )))

When the client receives this response, it should check if there is an error. If there is an error then it should show the user, else it should proceed. If there is no error then it should store the access_token in a javascript variable (called GOOGLE_OAUTH2_ACCESS_TOKEN) below, because it will need it shortly.
Now that you've validated the user's current login status, you can use the Google File Picker like this:

        var picker = new google.picker.PickerBuilder()
            .setOAuthToken(GOOGLE_OAUTH2_ACCESS_TOKEN.access_token)
            .addView(new google.picker.View(google.picker.ViewId.DOCS_IMAGES))
            .setCallback(pickerCallback)
            .build();
        picker.setVisible(true);

Above, you see that we stored the entire results of the ajax call in javascript variable GOOGLE_OAUTH2_ACCESS_TOKEN, and we're passing its access_token attribute to the Google File Picker.

And here is some sample javascript to use the Google Drive API to read the contents of a file selected above:

        grabFileFromGoogleDrive: function (fileId, imageBlock) {
            gapi.auth.setToken(GOOGLE_OAUTH2_ACCESS_TOKEN);
            var request = gapi.client.drive.files.get({
                'fileId': fileId
            });
            request.execute(function(resp) {
                // resp has fields such as: title, description, mimeType
                function gotFileContents(contents) {
                    // contents has binary data of file contents
                }
                grabFileContentsFromGoogleDrive(resp, gotFileContents);
            });
        }

        grabFileContentsFromGoogleDrive: function(file, callback) {
            if (file.downloadUrl) {
                var xhr = new XMLHttpRequest();
                var url = file.downloadUrl + '&access_token=' + GOOGLE_OAUTH2_ACCESS_TOKEN.access_token;
                xhr.open('GET', url);
                xhr.responseType = 'arraybuffer';  // only way to get binary files properly
                xhr.onload = function() {
                    callback(xhr.response);  // notice we're using response not responseText
                };
                xhr.onerror = function() {
                    callback(null);
                };
                xhr.send();
            } else {
              callback(null);
            }
        }

Enjoy!


Comments

comments powered by Disqus