google_drive 0.3.11 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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