google_drive 0.3.11 → 1.0.0.pre1

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.
Files changed (34) hide show
  1. checksums.yaml +7 -7
  2. data/README.rdoc +27 -10
  3. data/lib/google_drive/acl.rb +40 -58
  4. data/lib/google_drive/acl_entry.rb +76 -56
  5. data/lib/google_drive/api_client_fetcher.rb +49 -0
  6. data/lib/google_drive/collection.rb +69 -71
  7. data/lib/google_drive/file.rb +171 -128
  8. data/lib/google_drive/session.rb +234 -268
  9. data/lib/google_drive/spreadsheet.rb +19 -163
  10. data/lib/google_drive/util.rb +126 -17
  11. data/lib/google_drive/worksheet.rb +108 -80
  12. data/lib/google_drive.rb +63 -57
  13. data/lib/google_drive_v1/acl.rb +115 -0
  14. data/lib/google_drive_v1/acl_entry.rb +100 -0
  15. data/lib/google_drive_v1/api_client_fetcher.rb +47 -0
  16. data/lib/google_drive_v1/authentication_error.rb +14 -0
  17. data/lib/{google_drive → google_drive_v1}/basic_fetcher.rb +1 -1
  18. data/lib/{google_drive → google_drive_v1}/client_login_fetcher.rb +2 -2
  19. data/lib/google_drive_v1/collection.rb +167 -0
  20. data/lib/google_drive_v1/error.rb +12 -0
  21. data/lib/google_drive_v1/file.rb +258 -0
  22. data/lib/google_drive_v1/list.rb +119 -0
  23. data/lib/google_drive_v1/list_row.rb +88 -0
  24. data/lib/{google_drive → google_drive_v1}/oauth1_fetcher.rb +1 -1
  25. data/lib/{google_drive → google_drive_v1}/oauth2_fetcher.rb +2 -2
  26. data/lib/google_drive_v1/record.rb +31 -0
  27. data/lib/google_drive_v1/session.rb +522 -0
  28. data/lib/google_drive_v1/spreadsheet.rb +248 -0
  29. data/lib/google_drive_v1/table.rb +60 -0
  30. data/lib/google_drive_v1/util.rb +73 -0
  31. data/lib/google_drive_v1/worksheet.rb +498 -0
  32. data/lib/google_drive_v1.rb +148 -0
  33. metadata +112 -77
  34. data/doc_src/google_drive/acl_entry.rb +0 -33
@@ -8,11 +8,10 @@ require "rubygems"
8
8
  require "nokogiri"
9
9
  require "oauth"
10
10
  require "oauth2"
11
+ require "google/api_client"
11
12
 
12
13
  require "google_drive/util"
13
- require "google_drive/client_login_fetcher"
14
- require "google_drive/oauth1_fetcher"
15
- require "google_drive/oauth2_fetcher"
14
+ require "google_drive/api_client_fetcher"
16
15
  require "google_drive/error"
17
16
  require "google_drive/authentication_error"
18
17
  require "google_drive/spreadsheet"
@@ -23,7 +22,7 @@ require "google_drive/file"
23
22
 
24
23
  module GoogleDrive
25
24
 
26
- # Use GoogleDrive.login or GoogleDrive.saved_session to get
25
+ # Use GoogleDrive.login_with_oauth or GoogleDrive.saved_session to get
27
26
  # GoogleDrive::Session object.
28
27
  class Session
29
28
 
@@ -32,133 +31,103 @@ module GoogleDrive
32
31
 
33
32
  UPLOAD_CHUNK_SIZE = 512 * 1024
34
33
 
35
- # DEPRECATED: Will be removed in the next version.
36
- #
37
- # The same as GoogleDrive.login.
38
- def self.login(mail, password, proxy = nil)
39
- warn(
40
- "WARNING: GoogleDrive.login is deprecated and will be removed in the next version. " +
41
- "Use GoogleDrive.login_with_oauth instead.")
42
- session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
43
- session.login(mail, password)
44
- return session
45
- end
46
-
47
34
  # The same as GoogleDrive.login_with_oauth.
48
- def self.login_with_oauth(access_token, proxy = nil)
49
- if proxy
50
- warn(
51
- "WARNING: Specifying a proxy object is deprecated and will not work in the next version. " +
52
- "Set ENV[\"http_proxy\"] instead.")
53
- end
54
- case access_token
55
- when OAuth::AccessToken
56
- warn(
57
- "WARNING: Authorization with OAuth1 is deprecated and will not work in the next version. " +
58
- "Use OAuth2 instead.")
59
- raise(GoogleDrive::Error, "proxy is not supported with OAuth1.") if proxy
60
- fetcher = OAuth1Fetcher.new(access_token)
61
- when OAuth2::AccessToken
62
- fetcher = OAuth2Fetcher.new(access_token.token, proxy)
63
- when String
64
- fetcher = OAuth2Fetcher.new(access_token, proxy)
65
- else
66
- raise(GoogleDrive::Error,
67
- "access_token is neither String, OAuth2::Token nor OAuth::Token: %p" % access_token)
68
- end
69
- return Session.new(nil, fetcher)
35
+ def self.login_with_oauth(client_or_access_token, proxy = nil)
36
+ return Session.new(client_or_access_token, proxy)
70
37
  end
71
38
 
72
- # The same as GoogleDrive.restore_session.
73
- def self.restore_session(auth_tokens, proxy = nil)
74
- warn(
75
- "WARNING: GoogleDrive.restore_session is deprecated and will be removed in the next version. " +
76
- "Use GoogleDrive.login_with_oauth instead.")
77
- return Session.new(auth_tokens, nil, proxy)
78
- end
79
-
80
39
  # Creates a dummy GoogleDrive::Session object for testing.
81
40
  def self.new_dummy()
82
- return Session.new(nil, Object.new())
41
+ return Session.new(nil)
83
42
  end
84
43
 
85
- # DEPRECATED: Use GoogleDrive.restore_session instead.
86
- def initialize(auth_tokens = nil, fetcher = nil, proxy = nil)
87
- if fetcher
88
- @fetcher = fetcher
89
- else
90
- @fetcher = ClientLoginFetcher.new(auth_tokens || {}, proxy)
91
- end
92
- end
44
+ def initialize(client_or_access_token, proxy = nil)
93
45
 
94
- # Authenticates with given +mail+ and +password+, and updates current session object
95
- # if succeeds. Raises GoogleDrive::AuthenticationError if fails.
96
- # Google Apps account is supported.
97
- def login(mail, password)
98
- if !@fetcher.is_a?(ClientLoginFetcher)
99
- raise(GoogleDrive::Error,
100
- "Cannot call login for session created by login_with_oauth.")
101
- end
102
- begin
103
- @fetcher.auth_tokens = {
104
- :wise => authenticate(mail, password, :wise),
105
- :writely => authenticate(mail, password, :writely),
106
- }
107
- rescue GoogleDrive::Error => ex
108
- return true if @on_auth_fail && @on_auth_fail.call()
109
- raise(AuthenticationError, "Authentication failed for #{mail}: #{ex.message}")
46
+ if proxy
47
+ raise(
48
+ ArgumentError,
49
+ "Specifying a proxy object is no longer supported. Set ENV[\"http_proxy\"] instead.")
110
50
  end
111
- end
112
51
 
113
- # Authentication tokens.
114
- def auth_tokens
115
- warn(
116
- "WARNING: GoogleDrive::Session\#auth_tokens is deprecated and will be removed in the next version.")
117
- if !@fetcher.is_a?(ClientLoginFetcher)
118
- raise(GoogleDrive::Error,
119
- "Cannot call auth_tokens for session created by " +
120
- "login_with_oauth.")
52
+ if client_or_access_token
53
+ api_client_params = {
54
+ :application_name => "google_drive Ruby library",
55
+ :application_version => "0.4.0",
56
+ }
57
+ case client_or_access_token
58
+ when Google::APIClient
59
+ client = client_or_access_token
60
+ when String
61
+ client = Google::APIClient.new(api_client_params)
62
+ client.authorization.access_token = client_or_access_token
63
+ when OAuth2::AccessToken
64
+ client = Google::APIClient.new(api_client_params)
65
+ client.authorization.access_token = client_or_access_token.token
66
+ when OAuth::AccessToken
67
+ raise(
68
+ ArgumentError,
69
+ "Passing OAuth::AccessToken to login_with_oauth is no longer supported. " +
70
+ "You can use OAuth1 by passing Google::APIClient.")
71
+ else
72
+ raise(
73
+ ArgumentError,
74
+ ("client_or_access_token is neither Google::APIClient, " +
75
+ "String nor OAuth2::AccessToken: %p") %
76
+ client_or_access_token)
77
+ end
78
+ @fetcher = ApiClientFetcher.new(client)
79
+ else
80
+ @fetcher = nil
121
81
  end
122
- return @fetcher.auth_tokens
123
- end
124
82
 
125
- # Authentication token.
126
- def auth_token(auth = :wise)
127
- warn(
128
- "WARNING: GoogleDrive::Session\#auth_token is deprecated and will be removed in the next version.")
129
- return self.auth_tokens[auth]
130
83
  end
131
84
 
132
85
  # Proc or Method called when authentication has failed.
133
86
  # When this function returns +true+, it tries again.
134
- def on_auth_fail
135
- warn(
136
- "WARNING: GoogleDrive::Session\#on_auth_fail is deprecated and will be removed in the next version.")
137
- return @on_auth_fail
87
+ attr_accessor :on_auth_fail
88
+
89
+ def execute!(*args) #:nodoc:
90
+ return @fetcher.client.execute!(*args)
138
91
  end
139
92
 
140
- def on_auth_fail=(func)
141
- warn(
142
- "WARNING: GoogleDrive::Session\#on_auth_fail is deprecated and will be removed in the next version.")
143
- @on_auth_fail = func
93
+ # Returns the Google::APIClient object.
94
+ def client
95
+ return @fetcher.client
96
+ end
97
+
98
+ # Returns client.discovered_api("drive", "v2").
99
+ def drive
100
+ return @fetcher.drive
144
101
  end
145
102
 
146
103
  # Returns list of files for the user as array of GoogleDrive::File or its subclass.
147
- # You can specify query parameters described at
148
- # https://developers.google.com/google-apps/documents-list/#getting_a_list_of_documents_and_files
149
- #
150
- # files doesn't return collections unless "showfolders" => true is specified.
104
+ # You can specify parameters documented at
105
+ # https://developers.google.com/drive/v2/reference/files/list
151
106
  #
152
107
  # e.g.
153
108
  # session.files
154
- # session.files("title" => "hoge", "title-exact" => "true")
155
- def files(params = {})
156
- url = concat_url(
157
- "#{DOCS_BASE_URL}?v=3", "?" + encode_query(params))
158
- doc = request(:get, url, :auth => :writely)
159
- return doc.css("feed > entry").map(){ |e| entry_element_to_file(e) }
109
+ # session.files("q" => "title = 'hoge'")
110
+ # session.files("q" => ["title = ?", "hoge"]) # Same as above with a placeholder
111
+ #
112
+ # By default, it returns the first 100 files. You can get all files by calling with a block:
113
+ # session.files do |file|
114
+ # p file
115
+ # end
116
+ # Or passing "pageToken" parameter:
117
+ # page_token = nil
118
+ # begin
119
+ # (files, page_token) = session.files("pageToken" => page_token)
120
+ # p files
121
+ # end while page_token
122
+ def files(params = {}, &block)
123
+ params = convert_params(params)
124
+ return execute_paged!(
125
+ :api_method => self.drive.files.list,
126
+ :parameters => params,
127
+ :converter => proc(){ |af| wrap_api_file(af) },
128
+ &block)
160
129
  end
161
-
130
+
162
131
  # Returns GoogleDrive::File or its subclass whose title exactly matches +title+.
163
132
  # Returns nil if not found. If multiple files with the +title+ are found, returns
164
133
  # one of them.
@@ -169,36 +138,55 @@ module GoogleDrive
169
138
  if title.is_a?(Array)
170
139
  return self.root_collection.file_by_title(title)
171
140
  else
172
- return files("title" => title, "title-exact" => "true")[0]
141
+ return files("q" => ["title = ?", title], "maxResults" => 1)[0]
173
142
  end
174
143
  end
175
144
 
145
+ # Returns GoogleDrive::File or its subclass with a given +id+.
146
+ def file_by_id(id)
147
+ api_result = execute!(
148
+ :api_method => self.drive.files.get,
149
+ :parameters => { "fileId" => id })
150
+ return wrap_api_file(api_result.data)
151
+ end
152
+
153
+ # Returns GoogleDrive::File or its subclass with a given +url+. +url+ must be eitehr of:
154
+ # - URL of the page you open to access a document/spreadsheet in your browser
155
+ # - URL of worksheet-based feed of a spreadseet
156
+ def file_by_url(url)
157
+ return file_by_id(url_to_id(url))
158
+ end
159
+
176
160
  # Returns list of spreadsheets for the user as array of GoogleDrive::Spreadsheet.
177
161
  # You can specify query parameters e.g. "title", "title-exact".
178
162
  #
179
163
  # e.g.
180
164
  # session.spreadsheets
181
- # session.spreadsheets("title" => "hoge")
182
- # session.spreadsheets("title" => "hoge", "title-exact" => "true")
183
- def spreadsheets(params = {})
184
- url = concat_url(
185
- "#{DOCS_BASE_URL}/-/spreadsheet?v=3", "?" + encode_query(params))
186
- doc = request(:get, url, :auth => :writely)
187
- # The API may return non-spreadsheets too when title-exact is specified.
188
- # Probably a bug. For workaround, only returns Spreadsheet instances.
189
- return doc.css("feed > entry").
190
- map(){ |e| entry_element_to_file(e) }.
191
- select(){ |f| f.is_a?(Spreadsheet) }
165
+ # session.spreadsheets("q" => "title = 'hoge'")
166
+ # session.spreadsheets("q" => ["title = ?", "hoge"]) # Same as above with a placeholder
167
+ #
168
+ # By default, it returns the first 100 spreadsheets. See document of files method for how to get
169
+ # all spreadsheets.
170
+ def spreadsheets(params = {}, &block)
171
+ params = convert_params(params)
172
+ query = construct_and_query([
173
+ "mimeType = 'application/vnd.google-apps.spreadsheet'",
174
+ params["q"],
175
+ ])
176
+ return files(params.merge({"q" => query}), &block)
192
177
  end
193
178
 
194
179
  # Returns GoogleDrive::Spreadsheet with given +key+.
195
180
  #
196
181
  # e.g.
197
- # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
198
- # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
182
+ # # https://docs.google.com/spreadsheets/d/1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0/edit
183
+ # session.spreadsheet_by_key("1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0")
199
184
  def spreadsheet_by_key(key)
200
- url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
201
- return Spreadsheet.new(self, url)
185
+ file = file_by_id(key)
186
+ if !file.is_a?(Spreadsheet)
187
+ raise(GoogleDrive::Error, "The file with the ID is not a spreadsheet: %s" % key)
188
+ end
189
+ return file
202
190
  end
203
191
 
204
192
  # Returns GoogleDrive::Spreadsheet with given +url+. You must specify either of:
@@ -207,32 +195,23 @@ module GoogleDrive
207
195
  #
208
196
  # e.g.
209
197
  # session.spreadsheet_by_url(
210
- # "https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg")
198
+ # "https://docs.google.com/spreadsheets/d/1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0/edit")
211
199
  # session.spreadsheet_by_url(
212
200
  # "https://spreadsheets.google.com/feeds/" +
213
- # "worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
201
+ # "worksheets/1L3-kvwJblyW_TvjYD-7pE-AXxw5_bkb6S_MljuIPVL0/private/full")
214
202
  def spreadsheet_by_url(url)
215
- # Tries to parse it as URL of human-readable spreadsheet.
216
- uri = URI.parse(url)
217
- if ["spreadsheets.google.com", "docs.google.com"].include?(uri.host)
218
- case uri.path
219
- when /\/d\/([^\/]+)/
220
- return spreadsheet_by_key($1)
221
- when /\/ccc$/
222
- if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
223
- return spreadsheet_by_key($1)
224
- end
225
- end
203
+ file = file_by_url(url)
204
+ if !file.is_a?(Spreadsheet)
205
+ raise(GoogleDrive::Error, "The file with the URL is not a spreadsheet: %s" % url)
226
206
  end
227
- # Assumes the URL is worksheets feed URL.
228
- return Spreadsheet.new(self, url)
207
+ return file
229
208
  end
230
209
 
231
210
  # Returns GoogleDrive::Spreadsheet with given +title+.
232
211
  # Returns nil if not found. If multiple spreadsheets with the +title+ are found, returns
233
212
  # one of them.
234
213
  def spreadsheet_by_title(title)
235
- return spreadsheets({"title" => title, "title-exact" => "true"})[0]
214
+ return spreadsheets("q" => ["title = ?", title], "maxResults" => 1)[0]
236
215
  end
237
216
 
238
217
  # Returns GoogleDrive::Worksheet with given +url+.
@@ -243,15 +222,24 @@ module GoogleDrive
243
222
  # "http://spreadsheets.google.com/feeds/" +
244
223
  # "cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
245
224
  def worksheet_by_url(url)
246
- return Worksheet.new(self, nil, url)
225
+ if !(url =~
226
+ %r{^https?://spreadsheets.google.com/feeds/cells/(.*)/(.*)/private/full((\?.*)?)$})
227
+ raise(GoogleDrive::Error, "URL is not a cell-based feed URL: #{url}")
228
+ end
229
+ worksheet_feed_url = "https://spreadsheets.google.com/feeds/worksheets/#{$1}/private/full/#{$2}#{$3}"
230
+ worksheet_feed_entry = request(:get, worksheet_feed_url)
231
+ return Worksheet.new(self, nil, worksheet_feed_entry)
247
232
  end
248
233
 
249
234
  # Returns the root collection.
250
235
  def root_collection
251
- return Collection.new(self, Collection::ROOT_URL)
236
+ return @root_collection ||= file_by_id("root")
252
237
  end
253
238
 
254
239
  # Returns the top-level collections (direct children of the root collection).
240
+ #
241
+ # By default, it returns the first 100 collections. See document of files method for how to get
242
+ # all collections.
255
243
  def collections
256
244
  return self.root_collection.subcollections
257
245
  end
@@ -278,40 +266,26 @@ module GoogleDrive
278
266
  # "http://docs.google.com/feeds/default/private/full/folder%3A" +
279
267
  # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
280
268
  def collection_by_url(url)
281
- uri = URI.parse(url)
282
- if ["docs.google.com", "drive.google.com"].include?(uri.host) &&
283
- uri.fragment =~ /^folders\/(.+)$/
284
- # Looks like a URL of human-readable collection page. Converts to collection feed URL.
285
- url = "#{DOCS_BASE_URL}/folder%3A#{$1}"
269
+ file = file_by_url(url)
270
+ if !file.is_a?(Collection)
271
+ raise(GoogleDrive::Error, "The file with the URL is not a collection: %s" % url)
286
272
  end
287
- return Collection.new(self, to_v3_url(url))
273
+ return file
288
274
  end
289
275
 
290
276
  # Creates new spreadsheet and returns the new GoogleDrive::Spreadsheet.
291
277
  #
292
278
  # e.g.
293
279
  # session.create_spreadsheet("My new sheet")
294
- def create_spreadsheet(
295
- title = "Untitled",
296
- feed_url = "https://docs.google.com/feeds/documents/private/full")
297
-
298
- xml = <<-"EOS"
299
- <atom:entry
300
- xmlns:atom="http://www.w3.org/2005/Atom"
301
- xmlns:docs="http://schemas.google.com/docs/2007">
302
- <atom:category
303
- scheme="http://schemas.google.com/g/2005#kind"
304
- term="http://schemas.google.com/docs/2007#spreadsheet"
305
- label="spreadsheet"/>
306
- <atom:title>#{h(title)}</atom:title>
307
- </atom:entry>
308
- EOS
309
-
310
- doc = request(:post, feed_url, :data => xml, :auth => :writely)
311
- ss_url = doc.css(
312
- "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
313
- return Spreadsheet.new(self, ss_url, title)
314
-
280
+ def create_spreadsheet(title = "Untitled")
281
+ file = self.drive.files.insert.request_schema.new({
282
+ "title" => title,
283
+ "mimeType" => "application/vnd.google-apps.spreadsheet",
284
+ })
285
+ api_result = execute!(
286
+ :api_method => self.drive.files.insert,
287
+ :body_object => file)
288
+ return wrap_api_file(api_result.data)
315
289
  end
316
290
 
317
291
  # Uploads a file with the given +title+ and +content+.
@@ -330,7 +304,8 @@ module GoogleDrive
330
304
  # session.upload_from_string("hoge\tfoo\n", "Hoge", :content_type => "text/tab-separated-values")
331
305
  # session.upload_from_string("hoge,foo\n", "Hoge", :content_type => "text/tsv")
332
306
  def upload_from_string(content, title = "Untitled", params = {})
333
- return upload_from_io(StringIO.new(content), title, params)
307
+ media = new_upload_io(StringIO.new(content), params)
308
+ return upload_from_media(media, title, params)
334
309
  end
335
310
 
336
311
  # Uploads a local file.
@@ -347,113 +322,89 @@ module GoogleDrive
347
322
  # session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/plain")
348
323
  #
349
324
  # # Uploads a text file and converts to a Google Spreadsheet:
350
- # session.upload_from_file("/path/to/hoge.tsv", "Hoge")
351
325
  # session.upload_from_file("/path/to/hoge.csv", "Hoge")
352
- # session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/tab-separated-values")
353
326
  # session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/csv")
354
327
  def upload_from_file(path, title = nil, params = {})
355
328
  file_name = ::File.basename(path)
356
329
  params = {:file_name => file_name}.merge(params)
357
- open(path, "rb") do |f|
358
- return upload_from_io(f, title || file_name, params)
359
- end
330
+ media = new_upload_io(path, params)
331
+ return upload_from_media(media, title || file_name, params)
360
332
  end
361
-
333
+
362
334
  # Uploads a file. Reads content from +io+.
363
335
  # Returns a GoogleSpreadsheet::File object.
364
336
  def upload_from_io(io, title = "Untitled", params = {})
365
- doc = request(:get, "#{DOCS_BASE_URL}?v=3",
366
- :auth => :writely)
367
- initial_url = doc.css(
368
- "link[rel='http://schemas.google.com/g/2005#resumable-create-media']")[0]["href"]
369
- entry = upload_raw(:post, initial_url, io, title, params)
370
- return entry_element_to_file(entry)
337
+ media = new_upload_io(io, params)
338
+ return upload_from_media(media, title, params)
371
339
  end
372
340
 
373
- def upload_raw(method, url, io, title = "Untitled", params = {}) #:nodoc:
374
-
375
- params = {:convert => true}.merge(params)
376
- pos = io.pos
377
- io.seek(0, IO::SEEK_END)
378
- total_bytes = io.pos - pos
379
- io.pos = pos
380
- content_type = params[:content_type]
381
- if !content_type && params[:file_name]
382
- content_type = EXT_TO_CONTENT_TYPE[::File.extname(params[:file_name]).downcase]
383
- end
384
- if !content_type
385
- content_type = "application/octet-stream"
341
+ # Uploads a file. Reads content from +media+.
342
+ # Returns a GoogleSpreadsheet::File object.
343
+ def upload_from_media(media, title = "Untitled", params = {})
344
+ file = self.drive.files.insert.request_schema.new({
345
+ "title" => title,
346
+ })
347
+ api_result = execute!(
348
+ :api_method => self.drive.files.insert,
349
+ :body_object => file,
350
+ :media => media,
351
+ :parameters => {
352
+ "uploadType" => "multipart",
353
+ "convert" => params[:convert] == false ? "false" : "true",
354
+ })
355
+ return wrap_api_file(api_result.data)
356
+ end
357
+
358
+ def wrap_api_file(api_file) #:nodoc:
359
+ case api_file.mime_type
360
+ when "application/vnd.google-apps.folder"
361
+ return Collection.new(self, api_file)
362
+ when "application/vnd.google-apps.spreadsheet"
363
+ return Spreadsheet.new(self, api_file)
364
+ else
365
+ return File.new(self, api_file)
386
366
  end
387
-
388
- initial_xml = <<-"EOS"
389
- <entry xmlns="http://www.w3.org/2005/Atom"
390
- xmlns:docs="http://schemas.google.com/docs/2007">
391
- <title>#{h(title)}</title>
392
- </entry>
393
- EOS
394
-
395
- default_initial_header = {
396
- "Content-Type" => "application/atom+xml;charset=utf-8",
397
- "X-Upload-Content-Type" => content_type,
398
- "X-Upload-Content-Length" => total_bytes.to_s(),
399
- }
400
- initial_full_url = concat_url(url, params[:convert] ? "?convert=true" : "?convert=false")
401
- initial_response = request(method, initial_full_url,
402
- :header => default_initial_header.merge(params[:header] || {}),
403
- :data => initial_xml,
404
- :auth => :writely,
405
- :response_type => :response)
406
- upload_url = initial_response["location"]
407
-
408
- if total_bytes > 0
409
- sent_bytes = 0
410
- while data = io.read(UPLOAD_CHUNK_SIZE)
411
- content_range = "bytes %d-%d/%d" % [
412
- sent_bytes,
413
- sent_bytes + data.bytesize - 1,
414
- total_bytes,
415
- ]
416
- upload_header = {
417
- "Content-Type" => content_type,
418
- "Content-Range" => content_range,
419
- }
420
- doc = request(
421
- :put, upload_url, :header => upload_header, :data => data, :auth => :writely)
422
- sent_bytes += data.bytesize
367
+ end
368
+
369
+ def execute_paged!(opts, &block) #:nodoc:
370
+
371
+ if block
372
+
373
+ page_token = nil
374
+ begin
375
+ parameters = (opts[:parameters] || {}).merge({"pageToken" => page_token})
376
+ (items, page_token) = execute_paged!(opts.merge({:parameters => parameters}))
377
+ items.each(&block)
378
+ end while page_token
379
+
380
+ elsif opts[:parameters] && opts[:parameters].has_key?("pageToken")
381
+
382
+ api_result = self.execute!(
383
+ :api_method => opts[:api_method],
384
+ :parameters => opts[:parameters])
385
+ items = api_result.data.items.map() do |item|
386
+ opts[:converter] ? opts[:converter].call(item) : item
423
387
  end
388
+ return [items, api_result.data.next_page_token]
389
+
424
390
  else
425
- upload_header = {
426
- "Content-Type" => content_type,
427
- }
428
- doc = request(
429
- :put, upload_url, :header => upload_header, :data => "", :auth => :writely)
391
+
392
+ parameters = (opts[:parameters] || {}).merge({"pageToken" => nil})
393
+ (items, next_page_token) = execute_paged!(opts.merge({:parameters => parameters}))
394
+ return items
395
+
430
396
  end
431
397
 
432
- return doc.root
433
-
434
398
  end
435
399
 
436
- def entry_element_to_file(entry) #:nodoc:
437
- type, resource_id = entry.css("gd|resourceId").text.split(/:/)
438
- title = entry.css("title").text
439
- case type
440
- when "folder"
441
- return Collection.new(self, entry)
442
- when "spreadsheet"
443
- worksheets_feed_link = entry.css(
444
- "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]
445
- return Spreadsheet.new(self, worksheets_feed_link["href"], title)
446
- else
447
- return GoogleDrive::File.new(self, entry)
448
- end
449
- end
450
-
451
400
  def request(method, url, params = {}) #:nodoc:
452
401
 
453
402
  # Always uses HTTPS.
454
403
  url = url.gsub(%r{^http://}, "https://")
455
404
  data = params[:data]
456
405
  auth = params[:auth] || :wise
406
+ response_type = params[:response_type] || :xml
407
+
457
408
  if params[:header]
458
409
  extra_header = params[:header]
459
410
  elsif data
@@ -461,7 +412,7 @@ module GoogleDrive
461
412
  else
462
413
  extra_header = {}
463
414
  end
464
- response_type = params[:response_type] || :xml
415
+ extra_header = {"GData-Version" => "3.0"}.merge(extra_header)
465
416
 
466
417
  while true
467
418
  response = @fetcher.request_raw(method, url, data, extra_header, auth)
@@ -499,22 +450,37 @@ module GoogleDrive
499
450
  end
500
451
  end
501
452
 
502
- def authenticate(mail, password, auth)
503
- params = {
504
- "accountType" => "HOSTED_OR_GOOGLE",
505
- "Email" => mail,
506
- "Passwd" => password,
507
- "service" => auth.to_s(),
508
- "source" => "Gimite-RubyGoogleDrive-1.00",
509
- }
510
- header = {"Content-Type" => "application/x-www-form-urlencoded"}
511
- response = request(:post,
512
- "https://www.google.com/accounts/ClientLogin",
513
- :data => encode_query(params),
514
- :auth => :none,
515
- :header => header,
516
- :response_type => :raw)
517
- return response.slice(/^Auth=(.*)$/, 1)
453
+ def url_to_id(url)
454
+ uri = URI.parse(url)
455
+ if ["spreadsheets.google.com", "docs.google.com", "drive.google.com"].include?(uri.host)
456
+ case uri.path
457
+ # Document feed.
458
+ when /^\/feeds\/\w+\/private\/full\/\w+%3A(.*)$/
459
+ return $1
460
+ # Worksheets feed of a spreadsheet.
461
+ when /^\/feeds\/worksheets\/([^\/]+)/
462
+ return $1
463
+ # Human-readable new spreadsheet/document.
464
+ when /\/d\/([^\/]+)/
465
+ return $1
466
+ # Human-readable folder view.
467
+ when /\/folderview$/
468
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^id=(.*)$/ }
469
+ return $1
470
+ end
471
+ # Human-readable old spreadsheet.
472
+ when /\/ccc$/
473
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
474
+ return $1
475
+ end
476
+ end
477
+ case uri.fragment
478
+ # Human-readable collection page.
479
+ when /^folders\/(.+)$/
480
+ return $1
481
+ end
482
+ end
483
+ raise(GoogleDrive::Error, "The given URL is not a known Google Drive URL: %s" % url)
518
484
  end
519
485
 
520
486
  end