Class: GoogleDrive

Inherits:
Object
  • Object
show all
Defined in:
lib/google_drive.rb

Overview

Convience wrapper for Google Drive

You can override the location of the client secrets and oauth2 JSON files with the environment variables GOOGLE_CLIENT_SECRETS and GOOGLE_DRIVE_OAUTH.

If you plan to run Middleman on a server, you can use Google’s server to server authentication. This will kick in if you define the environment variables GOOGLE_OAUTH_PERSON, GOOGLE_OAUTH_ISSUER and either GOOGLE_OAUTH_KEYFILE or GOOGLE_OAUTH_PRIVATE_KEY.

Defined Under Namespace

Classes: ConfigurationError, CreateError, DoesNotExist, GoogleDriveError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeGoogleDrive

Constructor. Loads all params from envionment variables.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/google_drive.rb', line 23

def initialize
  @credentials = ENV['GOOGLE_DRIVE_OAUTH'] || File.expand_path(
    '~/.google_drive_oauth2.json')
  @client_secrets = ENV['GOOGLE_CLIENT_SECRETS'] || File.expand_path(
    '~/.google_client_secrets.json')

  @person = ENV['GOOGLE_OAUTH_PERSON']
  @issuer = ENV['GOOGLE_OAUTH_ISSUER']
  @key_path = ENV['GOOGLE_OAUTH_KEYFILE']
  @private_key = ENV['GOOGLE_OAUTH_PRIVATE_KEY']

  # try to read the file,
  # throw errors if not readable or not found
  if @key_path
    @key = Google::APIClient::KeyUtils.load_from_pkcs12(
      @key_path, 'notasecret')
  elsif @private_key
    @key = OpenSSL::PKey::RSA.new(
      @private_key, 'notasecret')
  end

  @_files = {}
  @_spreadsheets = {}

  do_auth
end

Instance Attribute Details

#clientObject (readonly)

Google API Client object



20
21
22
# File 'lib/google_drive.rb', line 20

def client
  @client
end

Instance Method Details

#clear_authObject

Delete cached credentials



245
246
247
# File 'lib/google_drive.rb', line 245

def clear_auth
  File.delete @credentials if @key.nil?
end

#copy(file_id, title = nil, visibility = :private) ⇒ Hash Also known as: copy_doc

Make a copy of a Google Drive file

Parameters:

  • file_id (String)

    file id

  • title (String) (defaults to: nil)

    title for the newly created file

Returns:

  • (Hash)

    hash containing the id/key and url of the new file



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/google_drive.rb', line 134

def copy(file_id, title = nil, visibility = :private)
  drive = @client.discovered_api('drive', 'v2')

  if title.nil?
    copied_file = drive.files.copy.request_schema.new
  else
    copied_file = drive.files.copy.request_schema.new('title' => title)
  end
  cp_resp = @client.execute(
    api_method: drive.files.copy,
    body_object: copied_file,
    parameters: { fileId: file_id, visibility: visibility.to_s.upcase })

  if cp_resp.error?
    fail CreateError, cp_resp.error_message
  else
    return { id: cp_resp.data['id'], url: cp_resp.data['alternateLink'] }
  end
end

#do_authObject

Authenticate with Google and create the @client object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/google_drive.rb', line 250

def do_auth
  if local?
    @client = Google::APIClient.new(
      application_name: 'Middleman',
      application_version: Middleman::GoogleDrive::VERSION
    )
    begin
      file_storage = Google::APIClient::FileStorage.new(@credentials)
    rescue URI::InvalidURIError
      File.delete @credentials
      file_storage = Google::APIClient::FileStorage.new(@credentials)
    end
    if file_storage.authorization.nil?
      unless File.exist? @client_secrets
        fail ConfigurationError, 'You need to create a client_secrets.json file and save it to ~/.google_client_secrets.json.'
      end
      puts "\nPlease login via your web browser. We opened the tab for you...\n\n      MSG\n      client_secrets = Google::APIClient::ClientSecrets.load(@client_secrets)\n      flow = Google::APIClient::InstalledAppFlow.new(\n        client_id: client_secrets.client_id,\n        client_secret: client_secrets.client_secret,\n        scope: ['https://www.googleapis.com/auth/drive']\n      )\n      @client.authorization = flow.authorize(file_storage)\n    else\n      @client.authorization = file_storage.authorization\n    end\n  else\n    @client = Google::APIClient.new(\n      application_name: 'Middleman',\n      application_version: Middleman::GoogleDrive::VERSION,\n      authorization: Signet::OAuth2::Client.new(\n        token_credential_uri: 'https://accounts.google.com/o/oauth2/token',\n        audience: 'https://accounts.google.com/o/oauth2/token',\n        person: @person,\n        issuer: @issuer,\n        signing_key: @key,\n        scope: ['https://www.googleapis.com/auth/drive']\n      )\n    )\n    @client.authorization.fetch_access_token!\n  end\n  nil\nend\n"

#export(file_id, type) ⇒ String

Export a file Returns the file contents

Parameters:

  • file_id (String)

    file id

  • type (:excel, :text, :html)

    export type

Returns:

  • (String)

    file contents



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/google_drive.rb', line 81

def export(file_id, type)
  list_resp = find(file_id)

  # decide which mimetype we want
  mime = mime_for(type).content_type

  # Grab the export url.
  if list_resp['exportLinks'] && list_resp['exportLinks'][mime]
    uri = list_resp['exportLinks'][mime]
  else
    raise "Google doesn't support exporting file id #{file_id} to #{type}"
  end

  # get the export
  get_resp = @client.execute(uri: uri)

  # die if there's an error
  fail GoogleDriveError, get_resp.error_message if get_resp.error?

  # contents
  get_resp.body
end

#export_to_file(file_id, type, filename = nil) ⇒ String

Export a file and save to disk Returns the local path to the file

Parameters:

  • file_id (String)

    file id

  • type (:excel, :text, :html)

    export type

  • filename (String) (defaults to: nil)

    where to save the spreadsheet

Returns:

  • (String)

    path to the excel file



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/google_drive.rb', line 111

def export_to_file(file_id, type, filename = nil)
  contents = export(file_id, type)

  if filename.nil?
    # get a temporary file. The export is binary, so open the tempfile in
    # write binary mode
    Tempfile.create(
        ['googledoc', ".#{type}"],
        binmode: mime_for(type.to_s).binary?) do |fp|
      filename = fp.path
      fp.write contents
    end
  else
    open(filename, 'wb') { |fp| fp.write contents }
  end
  filename
end

#find(file_id) ⇒ Hash

Find a Google Drive file Takes the key of a Google Drive file and returns a hash of meta data. The returned hash is formatted as a Google Drive resource.

Parameters:

  • file_id (String)

    file id

Returns:

  • (Hash)

    file meta data



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/google_drive.rb', line 59

def find(file_id)
  return @_files[file_id] unless @_files[file_id].nil?

  drive = @client.discovered_api('drive', 'v2')

  # get the file metadata
  resp = @client.execute(
    api_method: drive.files.get,
    parameters: { fileId: file_id })

  # die if there's an error
  fail GoogleDriveError, resp.error_message if resp.error?

  @_files[file_id] = resp.data
end

#load_microcopy(table) ⇒ Hash

Take a two-dimensional array from a spreadsheet and create a hash. The first column is used as the key, and the second column is the value. If the key occurs more than once, the value becomes an array to hold all the values associated with the key.

Parameters:

  • table (Array<Array>)

    2d array of cell values

Returns:

  • (Hash)

    spreadsheet contents



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/google_drive.rb', line 197

def load_microcopy(table)
  data = {}
  table.each_with_index do |row, i|
    next if i == 0 # skip the header row
    # Did we already create this key?
    if data.keys.include? row[0]
      # if the key name is reused, create an array with all the entries
      if data[row[0]].is_a? Array
        data[row[0]] << row[1]
      else
        data[row[0]] = [data[row[0]], row[1]]
      end
    else
      # add this row's key and value to the hash
      data[row[0]] = row[1]
    end
  end
  data
end

#load_table(table) ⇒ Array<Hash>

Take a two-dimensional array from a spreadsheet and create an array of hashes.

Parameters:

  • table (Array<Array>)

    2d array of cell values

Returns:

  • (Array<Hash>)

    spreadsheet contents



221
222
223
224
225
226
227
228
# File 'lib/google_drive.rb', line 221

def load_table(table)
  return [] if table.length < 2
  header = table.shift # Get the header row
  table.map do |row|
    # zip headers with current row, convert it to a hash
    header.zip(row).to_h unless row.nil?
  end
end

#local?Boolean

Returns true if we’re using local oauth2 (like on your computer).

Returns:

  • (Boolean)


240
241
242
# File 'lib/google_drive.rb', line 240

def local?
  @key.nil?
end

#mime_for(extension) ⇒ String?

Get the mime type from a file extension

Parameters:

  • extension (String)

    file ext

Returns:

  • (String, nil)

    mime type for the file



159
160
161
# File 'lib/google_drive.rb', line 159

def mime_for(extension)
  MIME::Types.of(extension.to_s).first
end

#prepare_spreadsheet(filename) ⇒ Hash

Parse a spreadsheet Reduces the spreadsheet to a no-frills hash, suitable for serializing and passing around.

Parameters:

  • filename (String)

    path to xls file

Returns:

  • (Hash)

    spreadsheet contents



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/google_drive.rb', line 170

def prepare_spreadsheet(filename)
  # open the file with RubyXL
  xls = RubyXL::Parser.parse(filename)
  data = {}
  xls.worksheets.each do |sheet|
    title = sheet.sheet_name
    # if the sheet is called microcopy, copy or ends with copy, we assume
    # the first column contains keys and the second contains values.
    # Like tarbell.
    if %w(microcopy copy).include?(title.downcase) ||
        title.downcase =~ /[ -_]copy$/
      data[title] = load_microcopy(sheet.extract_data)
    else
      # otherwise parse the sheet into a hash
      data[title] = load_table(sheet.extract_data)
    end
  end
  return data
end

#server?Boolean

Returns true if we’re using a private key to autheticate (like on a server).

Returns:

  • (Boolean)


234
235
236
# File 'lib/google_drive.rb', line 234

def server?
  !local?
end