Commit f6e544b

mo khan <mo@mokhan.ca>
2015-06-14 18:04:50
attempt to download with ruby client library.
1 parent 872942a
app/jobs/download_from_drive_job.rb
@@ -1,11 +1,29 @@
 class DownloadFromDriveJob < ActiveJob::Base
   queue_as :default
+  require 'google/api_client'
+
+  CLIENT_ID = "241601222378-kscpfqhpmc6059704mfcq8ckcp799dvn.apps.googleusercontent.com"
+  CLIENT_SECRET = ENV['GOOGLE_CLIENT_SECRET']
+  REDIRECT_URI = ENV['GOOGLE_REDIRECT_URI']
+  SCOPES = [
+    'https://www.googleapis.com/auth/drive',
+    'https://www.googleapis.com/auth/drive.install',
+    'https://www.googleapis.com/auth/drive.file',
+    'https://www.googleapis.com/auth/drive.readonly',
+    'https://www.googleapis.com/auth/drive.apps.readonly',
+  ]
 
   def perform(params)
     puts params.inspect
-    Dir.mktmpdir do |dir|
-      download_path = File.join(dir, params[:title])
-      `wget -O #{download_path} #{params[:downloadUrl]}`
-    end
+
+    drive = GoogleDrive.new
+    drive.build_client(drive.get_credentials())
+
+    #Dir.mktmpdir do |dir|
+      #download_path = File.join(dir, params[:title])
+      #`wget -O #{download_path} #{params[:downloadUrl]}`
+    #end
   end
 end
+
+ 
app/models/google_drive.rb
@@ -0,0 +1,183 @@
+##
+# Error raised when an error occurred while retrieving credentials.
+class GetCredentialsError < StandardError
+  ##
+  # Initialize a NoRefreshTokenError instance.
+  #
+  # @param [String] authorize_url
+  #   Authorization URL to redirect the user to in order to in order to request
+  #   offline access.
+  def initialize(authorization_url)
+    @authorization_url = authorization_url
+  end
+
+  def authorization_url=(authorization_url)
+    @authorization_url = authorization_url
+  end
+
+  def authorization_url
+    return @authorization_url
+  end
+end
+
+##
+# Error raised when a code exchange has failed.
+class CodeExchangeError < GetCredentialsError
+end
+
+##
+# Error raised when no refresh token has been found.
+class NoRefreshTokenError < GetCredentialsError
+end
+
+##
+# Error raised when no user ID could be retrieved.
+class NoUserIdError < StandardError
+end
+
+class GoogleDrive
+  ##
+  # Retrieved stored credentials for the provided user ID.
+  #
+  # @param [String] user_id
+  #   User's ID.
+  # @return [Signet::OAuth2::Client]
+  #  Stored OAuth 2.0 credentials if found, nil otherwise.
+  def get_stored_credentials(user_id)
+    raise NotImplementedError, 'get_stored_credentials is not implemented.'
+  end
+
+  ##
+  # Store OAuth 2.0 credentials in the application's database.
+  #
+  # @param [String] user_id
+  #   User's ID.
+  # @param [Signet::OAuth2::Client] credentials
+  #   OAuth 2.0 credentials to store.
+  def store_credentials(user_id, credentials)
+    raise NotImplementedError, 'store_credentials is not implemented.'
+  end
+
+  ##
+  # Exchange an authorization code for OAuth 2.0 credentials.
+  #
+  # @param [String] auth_code
+  #   Authorization code to exchange for OAuth 2.0 credentials.
+  # @return [Signet::OAuth2::Client]
+  #  OAuth 2.0 credentials.
+  def exchange_code(authorization_code)
+    client = Google::APIClient.new
+    client.authorization.client_id = CLIENT_ID
+    client.authorization.client_secret = CLIENT_SECRET
+    client.authorization.code = authorization_code
+    client.authorization.redirect_uri = REDIRECT_URI
+
+    begin
+      client.authorization.fetch_access_token!
+      return client.authorization
+    rescue Signet::AuthorizationError
+      raise CodeExchangeError.new(nil)
+    end
+  end
+
+  ##
+  # Send a request to the UserInfo API to retrieve the user's information.
+  #
+  # @param [Signet::OAuth2::Client] credentials
+  #   OAuth 2.0 credentials to authorize the request.
+  # @return [Google::APIClient::Schema::Oauth2::V2::Userinfo]
+  #   User's information.
+  def get_user_info(credentials)
+    client = Google::APIClient.new
+    client.authorization = credentials
+    oauth2 = client.discovered_api('oauth2', 'v2')
+    result = client.execute!(:api_method => oauth2.userinfo.get)
+    user_info = nil
+    if result.status == 200
+      user_info = result.data
+    else
+      puts "An error occurred: #{result.data['error']['message']}"
+    end
+    if user_info != nil && user_info.id != nil
+      return user_info
+    end
+    raise NoUserIdError, 'Unable to retrieve the e-mail address.'
+  end
+
+  ##
+  # Retrieve authorization URL.
+  #
+  # @param [String] email_address
+  #   User's e-mail address.
+  # @param [String] state
+  #   State for the authorization URL.
+  # @return [String]
+  #  Authorization URL to redirect the user to.
+  def get_authorization_url(email_address, state)
+    client = Google::APIClient.new
+    client.authorization.client_id = CLIENT_ID
+    client.authorization.redirect_uri = REDIRECT_URI
+    client.authorization.scope = SCOPES
+
+    return client.authorization.authorization_uri(
+      :approval_prompt => :force,
+      :access_type => :offline,
+      :user_id => email_address,
+      :state => state
+    ).to_s
+  end
+
+  ##
+  # Retrieve credentials using the provided authorization code.
+  #
+  #  This function exchanges the authorization code for an access token and queries
+  #  the UserInfo API to retrieve the user's e-mail address.
+  #  If a refresh token has been retrieved along with an access token, it is stored
+  #  in the application database using the user's e-mail address as key.
+  #  If no refresh token has been retrieved, the function checks in the application
+  #  database for one and returns it if found or raises a NoRefreshTokenError
+  #  with an authorization URL to redirect the user to.
+  #
+  # @param [String] auth_code
+  #   Authorization code to use to retrieve an access token.
+  # @param [String] state
+  #   State to set to the authorization URL in case of error.
+  # @return [Signet::OAuth2::Client]
+  #  OAuth 2.0 credentials containing an access and refresh token.
+  def get_credentials(authorization_code, state)
+    email_address = ''
+    begin
+      credentials = exchange_code(authorization_code)
+      user_info = get_user_info(credentials)
+      email_address = user_info.email
+      user_id = user_info.id
+      if credentials.refresh_token != nil
+        store_credentials(user_id, credentials)
+        return credentials
+      else
+        credentials = get_stored_credentials(user_id)
+        if credentials != nil && credentials.refresh_token != nil
+          return credentials
+        end
+      end
+    rescue CodeExchangeError => error
+      print 'An error occurred during code exchange.'
+      # Drive apps should try to retrieve the user and credentials for the current
+      # session.
+      # If none is available, redirect the user to the authorization URL.
+      error.authorization_url = get_authorization_url(email_address, state)
+      raise error
+    rescue NoUserIdError
+      print 'No user ID could be retrieved.'
+    end
+    authorization_url = get_authorization_url(email_address, state)
+    raise NoRefreshTokenError.new(authorization_url)
+  end
+
+  def build_client(credentials)
+    client = Google::APIClient.new
+    client.authorization = credentials
+    client = client.discovered_api('drive', 'v2')
+    client
+  end
+end
Gemfile
@@ -38,6 +38,7 @@ gem 'chartkick'
 gem 'groupdate'
 gem 'dalli'
 gem 'sequel'
+gem 'google-api-client'
 
 group :development, :test do
   # Call 'byebug' anywhere in the code to stop execution and get a debugger console
Gemfile.lock
@@ -36,7 +36,12 @@ GEM
       minitest (~> 5.1)
       thread_safe (~> 0.3, >= 0.3.4)
       tzinfo (~> 1.1)
+    addressable (2.3.8)
     arel (6.0.0)
+    autoparse (0.3.3)
+      addressable (>= 2.3.1)
+      extlib (>= 0.9.15)
+      multi_json (>= 1.0.0)
     axiom-types (0.1.1)
       descendants_tracker (~> 0.0.4)
       ice_nine (~> 0.11.0)
@@ -113,11 +118,14 @@ GEM
     equalizer (0.0.11)
     erubis (2.7.0)
     execjs (2.5.2)
+    extlib (0.9.16)
     factory_girl (4.5.0)
       activesupport (>= 3.0.0)
     factory_girl_rails (4.5.0)
       factory_girl (~> 4.5.0)
       railties (>= 3.0.0)
+    faraday (0.9.1)
+      multipart-post (>= 1.2, < 3)
     fast_stack (0.1.0)
       rake
       rake-compiler
@@ -131,6 +139,24 @@ GEM
       sass (>= 3.3.0, < 3.5)
     globalid (0.3.5)
       activesupport (>= 4.1.0)
+    google-api-client (0.8.6)
+      activesupport (>= 3.2)
+      addressable (~> 2.3)
+      autoparse (~> 0.3)
+      extlib (~> 0.9)
+      faraday (~> 0.9)
+      googleauth (~> 0.3)
+      launchy (~> 2.4)
+      multi_json (~> 1.10)
+      retriable (~> 1.4)
+      signet (~> 0.6)
+    googleauth (0.4.1)
+      faraday (~> 0.9)
+      jwt (~> 1.4)
+      logging (~> 2.0)
+      memoist (~> 0.12)
+      multi_json (= 1.11)
+      signet (~> 0.6)
     groupdate (2.4.0)
       activesupport (>= 3)
     highline (1.7.2)
@@ -154,11 +180,19 @@ GEM
       railties (>= 4.2.0)
       thor (>= 0.14, < 2.0)
     json (1.8.3)
+    jwt (1.5.0)
+    launchy (2.4.3)
+      addressable (~> 2.3)
     libv8 (3.16.14.7)
+    little-plugger (1.1.3)
+    logging (2.0.0)
+      little-plugger (~> 1.1)
+      multi_json (~> 1.10)
     loofah (2.0.2)
       nokogiri (>= 1.5.9)
     mail (2.6.3)
       mime-types (>= 1.16, < 3)
+    memoist (0.12.0)
     meta_request (0.3.4)
       callsite (~> 0.0, >= 0.0.11)
       rack-contrib (~> 1.1)
@@ -167,6 +201,7 @@ GEM
     mini_portile (0.6.2)
     minitest (5.7.0)
     multi_json (1.11.0)
+    multipart-post (2.0.0)
     net-scp (1.2.1)
       net-ssh (>= 2.6.5)
     net-ssh (2.9.2)
@@ -232,6 +267,7 @@ GEM
       http-cookie (>= 1.0.2, < 2.0)
       mime-types (>= 1.16, < 3.0)
       netrc (~> 0.7)
+    retriable (1.4.1)
     rspec-core (3.2.3)
       rspec-support (~> 3.2.0)
     rspec-expectations (3.2.1)
@@ -264,6 +300,12 @@ GEM
       json (~> 1.7, >= 1.7.7)
       rdoc (~> 4.0)
     sequel (4.23.0)
+    signet (0.6.1)
+      addressable (~> 2.3)
+      extlib (~> 0.9)
+      faraday (~> 0.9)
+      jwt (~> 1.5)
+      multi_json (~> 1.10)
     simplecov (0.10.0)
       docile (~> 1.1.0)
       json (~> 1.8)
@@ -351,6 +393,7 @@ DEPENDENCIES
   flamegraph
   foreman
   foundation-rails
+  google-api-client
   groupdate
   i18n-tasks
   jbuilder (~> 2.0)