google_drive2 3.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +136 -0
- data/lib/google_drive/access_token_credentials.rb +19 -0
- data/lib/google_drive/acl.rb +111 -0
- data/lib/google_drive/acl_entry.rb +181 -0
- data/lib/google_drive/api_client_fetcher.rb +59 -0
- data/lib/google_drive/authentication_error.rb +10 -0
- data/lib/google_drive/collection.rb +207 -0
- data/lib/google_drive/config.rb +36 -0
- data/lib/google_drive/error.rb +8 -0
- data/lib/google_drive/file.rb +266 -0
- data/lib/google_drive/list.rb +125 -0
- data/lib/google_drive/list_row.rb +89 -0
- data/lib/google_drive/response_code_error.rb +23 -0
- data/lib/google_drive/session.rb +733 -0
- data/lib/google_drive/spreadsheet.rb +135 -0
- data/lib/google_drive/util.rb +243 -0
- data/lib/google_drive/worksheet.rb +770 -0
- data/lib/google_drive.rb +35 -0
- metadata +196 -0
@@ -0,0 +1,733 @@
|
|
1
|
+
# Author: Hiroshi Ichikawa <http://gimite.net/>
|
2
|
+
# The license of this source is "New BSD Licence"
|
3
|
+
|
4
|
+
require 'cgi'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'nokogiri'
|
9
|
+
require 'googleauth'
|
10
|
+
|
11
|
+
require 'google_drive/util'
|
12
|
+
require 'google_drive/api_client_fetcher'
|
13
|
+
require 'google_drive/error'
|
14
|
+
require 'google_drive/authentication_error'
|
15
|
+
require 'google_drive/response_code_error'
|
16
|
+
require 'google_drive/spreadsheet'
|
17
|
+
require 'google_drive/worksheet'
|
18
|
+
require 'google_drive/collection'
|
19
|
+
require 'google_drive/file'
|
20
|
+
require 'google_drive/config'
|
21
|
+
require 'google_drive/access_token_credentials'
|
22
|
+
|
23
|
+
module GoogleDrive
|
24
|
+
# A session for Google Drive operations.
|
25
|
+
#
|
26
|
+
# Use from_credentials, from_access_token, from_service_account_key or
|
27
|
+
# from_config class method to construct a GoogleDrive::Session object.
|
28
|
+
class Session
|
29
|
+
include(Util)
|
30
|
+
extend(Util)
|
31
|
+
|
32
|
+
DEFAULT_SCOPE = [
|
33
|
+
'https://www.googleapis.com/auth/drive',
|
34
|
+
'https://spreadsheets.google.com/feeds/'
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
# Equivalent of either from_credentials or from_access_token.
|
38
|
+
def self.login_with_oauth(credentials_or_access_token, proxy = nil)
|
39
|
+
Session.new(credentials_or_access_token, proxy)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a dummy GoogleDrive::Session object for testing.
|
43
|
+
def self.new_dummy
|
44
|
+
Session.new(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Constructs a GoogleDrive::Session object from OAuth2 credentials such as
|
48
|
+
# Google::Auth::UserRefreshCredentials.
|
49
|
+
#
|
50
|
+
# See
|
51
|
+
# https://github.com/gimite/google-drive-ruby/blob/master/doc/authorization.md
|
52
|
+
# for a usage example.
|
53
|
+
#
|
54
|
+
# See from_config documentation for an explanation of client_options and
|
55
|
+
# request_options.
|
56
|
+
def self.from_credentials(credentials, client_options = nil, request_options = nil)
|
57
|
+
Session.new(credentials, nil, client_options, request_options)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Constructs a GoogleDrive::Session object from OAuth2 access token string.
|
61
|
+
#
|
62
|
+
# See from_config documentation for an explanation of client_options and
|
63
|
+
# request_options.
|
64
|
+
def self.from_access_token(access_token, client_options = nil, request_options = nil)
|
65
|
+
Session.new(access_token, nil, client_options, request_options)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Constructs a GoogleDrive::Session object from a service account key JSON.
|
69
|
+
#
|
70
|
+
# You can pass either the path to a JSON file, or an IO-like object with the
|
71
|
+
# JSON.
|
72
|
+
#
|
73
|
+
# See
|
74
|
+
# https://github.com/gimite/google-drive-ruby/blob/master/doc/authorization.md
|
75
|
+
# for a usage example.
|
76
|
+
#
|
77
|
+
# As with from_config, you can configure Google API client behavior with
|
78
|
+
# +client_options+ and +request_options+. Unlike in from_config, these
|
79
|
+
# are passed as positional arguments.
|
80
|
+
def self.from_service_account_key(
|
81
|
+
json_key_path_or_io, scope = DEFAULT_SCOPE, client_options = nil,
|
82
|
+
request_options = nil
|
83
|
+
)
|
84
|
+
if json_key_path_or_io.is_a?(String)
|
85
|
+
open(json_key_path_or_io) do |f|
|
86
|
+
from_service_account_key(f, scope, client_options, request_options)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
credentials = Google::Auth::ServiceAccountCredentials.make_creds(
|
90
|
+
json_key_io: json_key_path_or_io, scope: scope
|
91
|
+
)
|
92
|
+
Session.new(credentials, nil, client_options, request_options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns GoogleDrive::Session constructed from a config JSON file at
|
97
|
+
# +config+, potenially modified with +options+.
|
98
|
+
#
|
99
|
+
# +config+ is the path to the config file.
|
100
|
+
#
|
101
|
+
# This will prompt the credential via command line for the first time and
|
102
|
+
# save it to +config+ for later usages.
|
103
|
+
#
|
104
|
+
# See
|
105
|
+
# https://github.com/gimite/google-drive-ruby/blob/master/doc/authorization.md
|
106
|
+
# for a usage example.
|
107
|
+
#
|
108
|
+
# You can also provide a config object that must respond to:
|
109
|
+
# client_id
|
110
|
+
# client_secret
|
111
|
+
# refesh_token
|
112
|
+
# refresh_token=
|
113
|
+
# scope
|
114
|
+
# scope=
|
115
|
+
# save
|
116
|
+
#
|
117
|
+
# +options+ is a hash which may contain the following keys:
|
118
|
+
# client_secret: if present, this will overwrite config.client_secret
|
119
|
+
# client_id: if present, this will overwrite config.client_id
|
120
|
+
# client_options: a hash or ::Google::Apis::ClientOptions. See
|
121
|
+
# https://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/ClientOptions
|
122
|
+
# for an explanation of legal sub-fields.
|
123
|
+
# request_options: a hash or ::Google::Apis::RequestOptions. See
|
124
|
+
# https://www.rubydoc.info/github/google/google-api-ruby-client/Google/Apis/RequestOptions
|
125
|
+
# for an explanation of legal sub-fields.
|
126
|
+
def self.from_config(config, options = {})
|
127
|
+
if config.is_a?(String)
|
128
|
+
config_path = config
|
129
|
+
config = Config.new(config_path)
|
130
|
+
if config.type == 'service_account'
|
131
|
+
return from_service_account_key(
|
132
|
+
config_path, options[:scope] || DEFAULT_SCOPE, options[:client_options],
|
133
|
+
options[:request_options]
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
config.scope ||= DEFAULT_SCOPE
|
139
|
+
|
140
|
+
if options[:client_id] && options[:client_secret]
|
141
|
+
config.client_id = options[:client_id]
|
142
|
+
config.client_secret = options[:client_secret]
|
143
|
+
end
|
144
|
+
if !config.client_id || !config.client_secret
|
145
|
+
raise(
|
146
|
+
ArgumentError,
|
147
|
+
'client_id or client_secret is missing in the config. Follow ' \
|
148
|
+
'https://github.com/gimite/google-drive-ruby/blob/master/doc/authorization.md ' \
|
149
|
+
'to provide a valid config. google_drive library no longer provides ' \
|
150
|
+
'the default credential due to a limitation of Google API.'
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
credentials = Google::Auth::UserRefreshCredentials.new(
|
155
|
+
client_id: config.client_id,
|
156
|
+
client_secret: config.client_secret,
|
157
|
+
scope: config.scope,
|
158
|
+
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob'
|
159
|
+
)
|
160
|
+
|
161
|
+
if config.refresh_token
|
162
|
+
credentials.refresh_token = config.refresh_token
|
163
|
+
credentials.fetch_access_token!
|
164
|
+
else
|
165
|
+
$stderr.print(
|
166
|
+
format("\n1. Open this page:\n%s\n\n", credentials.authorization_uri)
|
167
|
+
)
|
168
|
+
$stderr.print('2. Enter the authorization code shown in the page: ')
|
169
|
+
credentials.code = $stdin.gets.chomp
|
170
|
+
credentials.fetch_access_token!
|
171
|
+
config.refresh_token = credentials.refresh_token
|
172
|
+
end
|
173
|
+
|
174
|
+
config.save
|
175
|
+
|
176
|
+
Session.new(
|
177
|
+
credentials, nil, options[:client_options], options[:request_options]
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def initialize(
|
182
|
+
credentials_or_access_token, proxy = nil, client_options = nil,
|
183
|
+
request_options = nil
|
184
|
+
)
|
185
|
+
if proxy
|
186
|
+
raise(
|
187
|
+
ArgumentError,
|
188
|
+
'Specifying a proxy object is no longer supported. ' \
|
189
|
+
'Set ENV["http_proxy"] instead.'
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
if credentials_or_access_token
|
194
|
+
if credentials_or_access_token.is_a?(String)
|
195
|
+
credentials = AccessTokenCredentials.new(credentials_or_access_token)
|
196
|
+
# Equivalent of credentials_or_access_token.is_a?(OAuth2::AccessToken),
|
197
|
+
# without adding dependency to "oauth2" library.
|
198
|
+
elsif credentials_or_access_token
|
199
|
+
.class
|
200
|
+
.ancestors
|
201
|
+
.any? { |m| m.name == 'OAuth2::AccessToken' }
|
202
|
+
credentials =
|
203
|
+
AccessTokenCredentials.new(credentials_or_access_token.token)
|
204
|
+
else
|
205
|
+
credentials = credentials_or_access_token
|
206
|
+
end
|
207
|
+
@fetcher = ApiClientFetcher.new(
|
208
|
+
credentials, client_options, request_options
|
209
|
+
)
|
210
|
+
else
|
211
|
+
@fetcher = nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Proc or Method called when authentication has failed.
|
216
|
+
# When this function returns +true+, it tries again.
|
217
|
+
attr_accessor :on_auth_fail
|
218
|
+
|
219
|
+
# Returns an instance of Google::Apis::DriveV3::DriveService.
|
220
|
+
def drive_service
|
221
|
+
@fetcher.drive
|
222
|
+
end
|
223
|
+
|
224
|
+
alias drive drive_service
|
225
|
+
|
226
|
+
# Returns an instance of Google::Apis::SheetsV4::SheetsService.
|
227
|
+
def sheets_service
|
228
|
+
@fetcher.sheets
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns list of files for the user as array of GoogleDrive::File or its
|
232
|
+
# subclass. You can specify parameters documented at
|
233
|
+
# https://developers.google.com/drive/v3/web/search-parameters
|
234
|
+
#
|
235
|
+
# e.g.
|
236
|
+
# session.files
|
237
|
+
# session.files(q: "name = 'hoge'")
|
238
|
+
# # Same as above with a placeholder
|
239
|
+
# session.files(q: ["name = ?", "hoge"])
|
240
|
+
#
|
241
|
+
# By default, it returns the first 100 files. You can get all files by
|
242
|
+
# calling with a block:
|
243
|
+
# session.files do |file|
|
244
|
+
# p file
|
245
|
+
# end
|
246
|
+
# Or passing "pageToken" parameter:
|
247
|
+
# page_token = nil
|
248
|
+
# begin
|
249
|
+
# (files, page_token) = session.files(page_token: page_token)
|
250
|
+
# p files
|
251
|
+
# end while page_token
|
252
|
+
def files(params = {}, &block)
|
253
|
+
params = convert_params(params)
|
254
|
+
execute_paged!(
|
255
|
+
method: drive_service.method(:list_files),
|
256
|
+
parameters: { fields: '*', supports_all_drives: true, include_items_from_all_drives: true }.merge(params),
|
257
|
+
items_method_name: :files,
|
258
|
+
converter: proc { |af| wrap_api_file(af) },
|
259
|
+
&block
|
260
|
+
)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Returns a file (including a spreadsheet and a folder) whose title exactly
|
264
|
+
# matches +title+.
|
265
|
+
#
|
266
|
+
# Returns an instance of GoogleDrive::File or its subclass
|
267
|
+
# (GoogleDrive::Spreadsheet, GoogleDrive::Collection). Returns nil if not
|
268
|
+
# found. If multiple files with the +title+ are found, returns one of them.
|
269
|
+
#
|
270
|
+
# If given an Array, traverses folders by title. e.g.:
|
271
|
+
# session.file_by_title(
|
272
|
+
# ["myfolder", "mysubfolder/even/w/slash", "myfile"])
|
273
|
+
def file_by_title(title)
|
274
|
+
if title.is_a?(Array)
|
275
|
+
root_collection.file_by_title(title)
|
276
|
+
else
|
277
|
+
files(q: ['name = ?', title], page_size: 1)[0]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
alias file_by_name file_by_title
|
282
|
+
|
283
|
+
# Returns a file (including a spreadsheet and a folder) with a given +id+.
|
284
|
+
#
|
285
|
+
# Returns an instance of GoogleDrive::File or its subclass
|
286
|
+
# (GoogleDrive::Spreadsheet, GoogleDrive::Collection).
|
287
|
+
def file_by_id(id)
|
288
|
+
api_file = drive_service.get_file(id, fields: '*', supports_all_drives: true)
|
289
|
+
wrap_api_file(api_file)
|
290
|
+
end
|
291
|
+
|
292
|
+
# Returns a file (including a spreadsheet and a folder) with a given +url+.
|
293
|
+
# +url+ must be the URL of the page you open to access a
|
294
|
+
# document/spreadsheet in your browser.
|
295
|
+
#
|
296
|
+
# Returns an instance of GoogleDrive::File or its subclass
|
297
|
+
# (GoogleDrive::Spreadsheet, GoogleDrive::Collection).
|
298
|
+
def file_by_url(url)
|
299
|
+
file_by_id(url_to_id(url))
|
300
|
+
end
|
301
|
+
|
302
|
+
# Returns list of spreadsheets for the user as array of
|
303
|
+
# GoogleDrive::Spreadsheet.
|
304
|
+
# You can specify parameters documented at
|
305
|
+
# https://developers.google.com/drive/v3/web/search-parameters
|
306
|
+
#
|
307
|
+
# e.g.
|
308
|
+
# session.spreadsheets
|
309
|
+
# session.spreadsheets(q: "name = 'hoge'")
|
310
|
+
# # Same as above with a placeholder
|
311
|
+
# session.spreadsheets(q: ["name = ?", "hoge"])
|
312
|
+
#
|
313
|
+
# By default, it returns the first 100 spreadsheets. See document of files
|
314
|
+
# method for how to get all spreadsheets.
|
315
|
+
def spreadsheets(params = {}, &block)
|
316
|
+
params = convert_params(params)
|
317
|
+
query = construct_and_query(
|
318
|
+
[
|
319
|
+
"mimeType = 'application/vnd.google-apps.spreadsheet'",
|
320
|
+
params[:q]
|
321
|
+
]
|
322
|
+
)
|
323
|
+
files(params.merge(q: query), &block)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Returns GoogleDrive::Spreadsheet with given +key+.
|
327
|
+
#
|
328
|
+
# e.g.
|
329
|
+
# # https://docs.google.com/spreadsheets/d/1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0/edit
|
330
|
+
# session.spreadsheet_by_key(
|
331
|
+
# "1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0")
|
332
|
+
def spreadsheet_by_key(key)
|
333
|
+
file = file_by_id(key)
|
334
|
+
unless file.is_a?(Spreadsheet)
|
335
|
+
raise(
|
336
|
+
GoogleDrive::Error,
|
337
|
+
format('The file with the ID is not a spreadsheet: %s', key)
|
338
|
+
)
|
339
|
+
end
|
340
|
+
file
|
341
|
+
end
|
342
|
+
|
343
|
+
# Returns GoogleDrive::Spreadsheet with given +url+. You must specify either
|
344
|
+
# of:
|
345
|
+
# - URL of the page you open to access the spreadsheet in your browser
|
346
|
+
# - URL of worksheet-based feed of the spreadseet
|
347
|
+
#
|
348
|
+
# e.g.
|
349
|
+
# session.spreadsheet_by_url(
|
350
|
+
# "https://docs.google.com/spreadsheets/d/" \
|
351
|
+
# "1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0/edit")
|
352
|
+
def spreadsheet_by_url(url)
|
353
|
+
file = file_by_url(url)
|
354
|
+
unless file.is_a?(Spreadsheet)
|
355
|
+
raise(
|
356
|
+
GoogleDrive::Error,
|
357
|
+
format('The file with the URL is not a spreadsheet: %s', url)
|
358
|
+
)
|
359
|
+
end
|
360
|
+
file
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns GoogleDrive::Spreadsheet with given +title+.
|
364
|
+
# Returns nil if not found. If multiple spreadsheets with the +title+ are
|
365
|
+
# found, returns one of them.
|
366
|
+
def spreadsheet_by_title(title)
|
367
|
+
spreadsheets(q: ['name = ?', title], page_size: 1)[0]
|
368
|
+
end
|
369
|
+
|
370
|
+
alias spreadsheet_by_name spreadsheet_by_title
|
371
|
+
|
372
|
+
# Returns GoogleDrive::Worksheet with given +url+.
|
373
|
+
# You must specify URL of either worksheet feed or cell-based feed of the
|
374
|
+
# worksheet.
|
375
|
+
#
|
376
|
+
# e.g.:
|
377
|
+
# # Worksheet feed URL
|
378
|
+
# session.worksheet_by_url(
|
379
|
+
# "https://spreadsheets.google.com/feeds/worksheets/" \
|
380
|
+
# "1smypkyAz4STrKO4Zkos5Z4UPUJKvvgIza32LnlQ7OGw/private/full/od7")
|
381
|
+
# # Cell-based feed URL
|
382
|
+
# session.worksheet_by_url(
|
383
|
+
# "https://spreadsheets.google.com/feeds/cells/" \
|
384
|
+
# "1smypkyAz4STrKO4Zkos5Z4UPUJKvvgIza32LnlQ7OGw/od7/private/full")
|
385
|
+
def worksheet_by_url(url)
|
386
|
+
case url
|
387
|
+
when %r{^https?://spreadsheets.google.com/feeds/worksheets/(.*)/.*/full/(.*)$}
|
388
|
+
spreadsheet_id = Regexp.last_match(1)
|
389
|
+
worksheet_feed_id = Regexp.last_match(2)
|
390
|
+
when %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full(\?.*)?$}
|
391
|
+
spreadsheet_id = Regexp.last_match(1)
|
392
|
+
worksheet_feed_id = Regexp.last_match(2)
|
393
|
+
else
|
394
|
+
raise(
|
395
|
+
GoogleDrive::Error,
|
396
|
+
'URL is neither a worksheet feed URL nor a cell-based feed URL: ' \
|
397
|
+
"#{url}"
|
398
|
+
)
|
399
|
+
end
|
400
|
+
|
401
|
+
spreadsheet = spreadsheet_by_key(spreadsheet_id)
|
402
|
+
worksheet = spreadsheet.worksheets.find{ |ws| ws.worksheet_feed_id == worksheet_feed_id }
|
403
|
+
unless worksheet
|
404
|
+
raise(
|
405
|
+
GoogleDrive::Error,
|
406
|
+
"Worksheet not found for the given URL: #{url}"
|
407
|
+
)
|
408
|
+
end
|
409
|
+
worksheet
|
410
|
+
end
|
411
|
+
|
412
|
+
# Returns the root folder.
|
413
|
+
def root_collection
|
414
|
+
@root_collection ||= file_by_id('root')
|
415
|
+
end
|
416
|
+
|
417
|
+
alias root_folder root_collection
|
418
|
+
|
419
|
+
# Returns the top-level folders (direct children of the root folder).
|
420
|
+
#
|
421
|
+
# By default, it returns the first 100 folders. See document of files method
|
422
|
+
# for how to get all folders.
|
423
|
+
def collections(params = {}, &block)
|
424
|
+
root_collection.subcollections(params, &block)
|
425
|
+
end
|
426
|
+
|
427
|
+
alias folders collections
|
428
|
+
|
429
|
+
# Returns a top-level folder whose title exactly matches +title+ as
|
430
|
+
# GoogleDrive::Collection.
|
431
|
+
# Returns nil if not found. If multiple folders with the +title+ are found,
|
432
|
+
# returns one of them.
|
433
|
+
def collection_by_title(title)
|
434
|
+
root_collection.subcollection_by_title(title)
|
435
|
+
end
|
436
|
+
|
437
|
+
alias folders_by_name collection_by_title
|
438
|
+
|
439
|
+
# Returns GoogleDrive::Collection with given +url+.
|
440
|
+
#
|
441
|
+
# You must specify the URL of the page you get when you go to
|
442
|
+
# https://drive.google.com/ with your browser and open a folder.
|
443
|
+
#
|
444
|
+
# e.g.
|
445
|
+
# session.collection_by_url(
|
446
|
+
# "https://drive.google.com/drive/folders/" \
|
447
|
+
# "1u99gpfHIk08RVK5q_vXxUqkxR1r6FUJH")
|
448
|
+
def collection_by_url(url)
|
449
|
+
file = file_by_url(url)
|
450
|
+
unless file.is_a?(Collection)
|
451
|
+
raise(
|
452
|
+
GoogleDrive::Error,
|
453
|
+
format('The file with the URL is not a folder: %s', url)
|
454
|
+
)
|
455
|
+
end
|
456
|
+
file
|
457
|
+
end
|
458
|
+
|
459
|
+
alias folder_by_url collection_by_url
|
460
|
+
|
461
|
+
# Returns GoogleDrive::Collection with given +id+.
|
462
|
+
#
|
463
|
+
# e.g.
|
464
|
+
# # https://drive.google.com/drive/folders/1rPPuzAew4tO3ORc88Vz1JscPCcnrX7-J
|
465
|
+
# session.collection_by_id("1rPPuzAew4tO3ORc88Vz1JscPCcnrX7-J")
|
466
|
+
def collection_by_id(id)
|
467
|
+
file = file_by_id(id)
|
468
|
+
unless file.is_a?(Collection)
|
469
|
+
raise(
|
470
|
+
GoogleDrive::Error,
|
471
|
+
format('The file with the ID is not a folder: %s', id)
|
472
|
+
)
|
473
|
+
end
|
474
|
+
file
|
475
|
+
end
|
476
|
+
|
477
|
+
alias folder_by_id collection_by_id
|
478
|
+
|
479
|
+
# Creates a top-level folder with given title. Returns GoogleDrive::Collection
|
480
|
+
# object.
|
481
|
+
def create_collection(title, file_properties = {})
|
482
|
+
create_file(title, file_properties.merge(mime_type: 'application/vnd.google-apps.folder'))
|
483
|
+
end
|
484
|
+
|
485
|
+
alias create_folder create_collection
|
486
|
+
|
487
|
+
# Creates a spreadsheet with given title. Returns GoogleDrive::Spreadsheet
|
488
|
+
# object.
|
489
|
+
#
|
490
|
+
# e.g.
|
491
|
+
# session.create_spreadsheet("My new sheet")
|
492
|
+
def create_spreadsheet(title = 'Untitled', file_properties = {})
|
493
|
+
create_file(title, file_properties.merge(mime_type: 'application/vnd.google-apps.spreadsheet'))
|
494
|
+
end
|
495
|
+
|
496
|
+
# Creates a file with given title and properties. Returns objects
|
497
|
+
# with the following types: GoogleDrive::Spreadsheet, GoogleDrive::File,
|
498
|
+
# GoogleDrive::Collection
|
499
|
+
#
|
500
|
+
# You can pass a MIME Type using the file_properties-function parameter,
|
501
|
+
# for example: create_file('Document Title', mime_type: 'application/vnd.google-apps.document')
|
502
|
+
#
|
503
|
+
# A list of available Drive MIME Types can be found here:
|
504
|
+
# https://developers.google.com/drive/v3/web/mime-types
|
505
|
+
def create_file(title, file_properties = {})
|
506
|
+
file_metadata = {
|
507
|
+
name: title,
|
508
|
+
}.merge(file_properties)
|
509
|
+
|
510
|
+
file = drive_service.create_file(
|
511
|
+
file_metadata, fields: '*', supports_all_drives: true
|
512
|
+
)
|
513
|
+
|
514
|
+
wrap_api_file(file)
|
515
|
+
end
|
516
|
+
|
517
|
+
# Uploads a file with the given +title+ and +content+.
|
518
|
+
# Returns a GoogleSpreadsheet::File object.
|
519
|
+
#
|
520
|
+
# e.g.
|
521
|
+
# # Uploads and converts to a Google Docs document:
|
522
|
+
# session.upload_from_string(
|
523
|
+
# "Hello world.", "Hello", content_type: "text/plain")
|
524
|
+
#
|
525
|
+
# # Uploads without conversion:
|
526
|
+
# session.upload_from_string(
|
527
|
+
# "Hello world.", "Hello", content_type: "text/plain", convert: false)
|
528
|
+
#
|
529
|
+
# # Uploads and converts to a Google Spreadsheet:
|
530
|
+
# session.upload_from_string(
|
531
|
+
# "hoge\tfoo\n", "Hoge", content_type: "text/tab-separated-values")
|
532
|
+
# session.upload_from_string(
|
533
|
+
# "hoge,foo\n", "Hoge", content_type: "text/tsv")
|
534
|
+
def upload_from_string(content, title = 'Untitled', params = {})
|
535
|
+
upload_from_source(StringIO.new(content), title, params)
|
536
|
+
end
|
537
|
+
|
538
|
+
# Uploads a local file.
|
539
|
+
# Returns a GoogleSpreadsheet::File object.
|
540
|
+
#
|
541
|
+
# e.g.
|
542
|
+
# # Uploads a text file and converts to a Google Docs document:
|
543
|
+
# session.upload_from_file("/path/to/hoge.txt")
|
544
|
+
#
|
545
|
+
# # Uploads without conversion:
|
546
|
+
# session.upload_from_file("/path/to/hoge.txt", "Hoge", convert: false)
|
547
|
+
#
|
548
|
+
# # Uploads with explicit content type:
|
549
|
+
# session.upload_from_file(
|
550
|
+
# "/path/to/hoge", "Hoge", content_type: "text/plain")
|
551
|
+
#
|
552
|
+
# # Uploads a text file and converts to a Google Spreadsheet:
|
553
|
+
# session.upload_from_file("/path/to/hoge.csv", "Hoge")
|
554
|
+
# session.upload_from_file(
|
555
|
+
# "/path/to/hoge", "Hoge", content_type: "text/csv")
|
556
|
+
def upload_from_file(path, title = nil, params = {})
|
557
|
+
# TODO: Add a feature to upload to a folder.
|
558
|
+
file_name = ::File.basename(path)
|
559
|
+
default_content_type =
|
560
|
+
EXT_TO_CONTENT_TYPE[::File.extname(file_name).downcase] ||
|
561
|
+
'application/octet-stream'
|
562
|
+
upload_from_source(
|
563
|
+
path,
|
564
|
+
title || file_name,
|
565
|
+
{ content_type: default_content_type }.merge(params)
|
566
|
+
)
|
567
|
+
end
|
568
|
+
|
569
|
+
# Uploads a file. Reads content from +io+.
|
570
|
+
# Returns a GoogleDrive::File object.
|
571
|
+
def upload_from_io(io, title = 'Untitled', params = {})
|
572
|
+
upload_from_source(io, title, params)
|
573
|
+
end
|
574
|
+
|
575
|
+
# @api private
|
576
|
+
def wrap_api_file(api_file)
|
577
|
+
case api_file.mime_type
|
578
|
+
when 'application/vnd.google-apps.folder'
|
579
|
+
Collection.new(self, api_file)
|
580
|
+
when 'application/vnd.google-apps.spreadsheet'
|
581
|
+
Spreadsheet.new(self, api_file)
|
582
|
+
else
|
583
|
+
File.new(self, api_file)
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# @api private
|
588
|
+
def execute_paged!(opts, &block)
|
589
|
+
if block
|
590
|
+
page_token = nil
|
591
|
+
loop do
|
592
|
+
parameters =
|
593
|
+
(opts[:parameters] || {}).merge(page_token: page_token)
|
594
|
+
(items, page_token) =
|
595
|
+
execute_paged!(opts.merge(parameters: parameters))
|
596
|
+
items.each(&block)
|
597
|
+
break unless page_token
|
598
|
+
end
|
599
|
+
|
600
|
+
elsif opts[:parameters] && opts[:parameters].key?(:page_token)
|
601
|
+
response = opts[:method].call(**opts[:parameters])
|
602
|
+
items = response.__send__(opts[:items_method_name]).map do |item|
|
603
|
+
opts[:converter] ? opts[:converter].call(item) : item
|
604
|
+
end
|
605
|
+
[items, response.next_page_token]
|
606
|
+
|
607
|
+
else
|
608
|
+
parameters = (opts[:parameters] || {}).merge(page_token: nil)
|
609
|
+
(items,) = execute_paged!(opts.merge(parameters: parameters))
|
610
|
+
items
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# @api private
|
615
|
+
def request(method, url, params = {})
|
616
|
+
# Always uses HTTPS.
|
617
|
+
url = url.gsub(%r{^http://}, 'https://')
|
618
|
+
data = params[:data]
|
619
|
+
auth = params[:auth] || :wise
|
620
|
+
response_type = params[:response_type] || :xml
|
621
|
+
|
622
|
+
extra_header = if params[:header]
|
623
|
+
params[:header]
|
624
|
+
elsif data
|
625
|
+
{
|
626
|
+
'Content-Type' => 'application/atom+xml;charset=utf-8'
|
627
|
+
}
|
628
|
+
else
|
629
|
+
{}
|
630
|
+
end
|
631
|
+
extra_header = { 'GData-Version' => '3.0' }.merge(extra_header)
|
632
|
+
|
633
|
+
loop do
|
634
|
+
response = @fetcher.request_raw(method, url, data, extra_header, auth)
|
635
|
+
next if response.code == '401' && @on_auth_fail && @on_auth_fail.call
|
636
|
+
unless response.code =~ /^2/
|
637
|
+
raise(
|
638
|
+
(response.code == '401' ? AuthenticationError : ResponseCodeError)
|
639
|
+
.new(response.code, response.body, method, url)
|
640
|
+
)
|
641
|
+
end
|
642
|
+
return convert_response(response, response_type)
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
def inspect
|
647
|
+
format('#<%p:0x%x>', self.class, object_id)
|
648
|
+
end
|
649
|
+
|
650
|
+
private
|
651
|
+
|
652
|
+
def upload_from_source(source, title, params = {})
|
653
|
+
api_params = {
|
654
|
+
upload_source: source,
|
655
|
+
content_type: 'application/octet-stream',
|
656
|
+
fields: '*',
|
657
|
+
supports_all_drives: true
|
658
|
+
}
|
659
|
+
for k, v in params
|
660
|
+
unless %i[convert convert_mime_type parents].include?(k)
|
661
|
+
api_params[k] = v
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
file_metadata = { name: title }
|
666
|
+
content_type = api_params[:content_type]
|
667
|
+
if params[:convert_mime_type]
|
668
|
+
file_metadata[:mime_type] = params[:convert_mime_type]
|
669
|
+
elsif params.fetch(:convert, true) &&
|
670
|
+
IMPORTABLE_CONTENT_TYPE_MAP.key?(content_type)
|
671
|
+
file_metadata[:mime_type] = IMPORTABLE_CONTENT_TYPE_MAP[content_type]
|
672
|
+
end
|
673
|
+
file_metadata[:parents] = params[:parents] if params[:parents]
|
674
|
+
|
675
|
+
file = drive_service.create_file(file_metadata, **api_params)
|
676
|
+
wrap_api_file(file)
|
677
|
+
end
|
678
|
+
|
679
|
+
def convert_response(response, response_type)
|
680
|
+
case response_type
|
681
|
+
when :xml
|
682
|
+
Nokogiri.XML(response.body)
|
683
|
+
when :raw
|
684
|
+
response.body
|
685
|
+
when :response
|
686
|
+
response
|
687
|
+
else
|
688
|
+
raise(GoogleDrive::Error,
|
689
|
+
format('Unknown params[:response_type]: %s', response_type))
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
def url_to_id(url)
|
694
|
+
uri = URI.parse(url)
|
695
|
+
if ['spreadsheets.google.com', 'docs.google.com', 'drive.google.com']
|
696
|
+
.include?(uri.host)
|
697
|
+
case uri.path
|
698
|
+
# Document feed.
|
699
|
+
when /^\/feeds\/\w+\/private\/full\/\w+%3A(.*)$/
|
700
|
+
return Regexp.last_match(1)
|
701
|
+
# Worksheets feed of a spreadsheet.
|
702
|
+
when /^\/feeds\/worksheets\/([^\/]+)/
|
703
|
+
return Regexp.last_match(1)
|
704
|
+
# Human-readable new spreadsheet/document.
|
705
|
+
when /\/d\/([^\/]+)/
|
706
|
+
return Regexp.last_match(1)
|
707
|
+
# Human-readable new folder page.
|
708
|
+
when /^\/drive\/[^\/]+\/([^\/]+)/
|
709
|
+
return Regexp.last_match(1)
|
710
|
+
# Human-readable old folder view.
|
711
|
+
when /\/folderview$/
|
712
|
+
if (uri.query || '').split(/&/).find { |s| s =~ /^id=(.*)$/ }
|
713
|
+
return Regexp.last_match(1)
|
714
|
+
end
|
715
|
+
# Human-readable old spreadsheet.
|
716
|
+
when /\/ccc$/
|
717
|
+
if (uri.query || '').split(/&/).find { |s| s =~ /^key=(.*)$/ }
|
718
|
+
return Regexp.last_match(1)
|
719
|
+
end
|
720
|
+
end
|
721
|
+
case uri.fragment
|
722
|
+
# Human-readable old folder page.
|
723
|
+
when /^folders\/(.+)$/
|
724
|
+
return Regexp.last_match(1)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
raise(
|
728
|
+
GoogleDrive::Error,
|
729
|
+
format('The given URL is not a known Google Drive URL: %s', url)
|
730
|
+
)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|