google_drive 1.0.4 → 1.0.5

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.
@@ -1,119 +0,0 @@
1
- # Author: Hiroshi Ichikawa <http://gimite.net/>
2
- # The license of this source is "New BSD Licence"
3
-
4
- require "google_drive_v0/util"
5
- require "google_drive_v0/error"
6
- require "google_drive_v0/list_row"
7
-
8
-
9
- module GoogleDriveV0
10
-
11
- # Provides access to cells using column names.
12
- # Use GoogleDriveV0::Worksheet#list to get GoogleDriveV0::List object.
13
- #--
14
- # This is implemented as wrapper of GoogleDriveV0::Worksheet i.e. using cells
15
- # feed, not list feed. In this way, we can easily provide consistent API as
16
- # GoogleDriveV0::Worksheet using save()/reload().
17
- class List
18
-
19
- include(Enumerable)
20
-
21
- def initialize(worksheet) #:nodoc:
22
- @worksheet = worksheet
23
- end
24
-
25
- # Number of non-empty rows in the worksheet excluding the first row.
26
- def size
27
- return @worksheet.num_rows - 1
28
- end
29
-
30
- # Returns Hash-like object (GoogleDriveV0::ListRow) for the row with the
31
- # index. Keys of the object are colum names (the first row).
32
- # The second row has index 0.
33
- #
34
- # Note that updates to the returned object are not sent to the server until
35
- # you call GoogleDriveV0::Worksheet#save().
36
- def [](index)
37
- return ListRow.new(self, index)
38
- end
39
-
40
- # Updates the row with the index with the given Hash object.
41
- # Keys of +hash+ are colum names (the first row).
42
- # The second row has index 0.
43
- #
44
- # Note that update is not sent to the server until
45
- # you call GoogleDriveV0::Worksheet#save().
46
- def []=(index, hash)
47
- self[index].replace(hash)
48
- end
49
-
50
- # Iterates over Hash-like object (GoogleDriveV0::ListRow) for each row
51
- # (except for the first row).
52
- # Keys of the object are colum names (the first row).
53
- def each(&block)
54
- for i in 0...self.size
55
- yield(self[i])
56
- end
57
- end
58
-
59
- # Column names i.e. the contents of the first row.
60
- # Duplicates are removed.
61
- def keys
62
- return (1..@worksheet.num_cols).map(){ |i| @worksheet[1, i] }.uniq()
63
- end
64
-
65
- # Updates column names i.e. the contents of the first row.
66
- #
67
- # Note that update is not sent to the server until
68
- # you call GoogleDriveV0::Worksheet#save().
69
- def keys=(ary)
70
- for i in 1..ary.size
71
- @worksheet[1, i] = ary[i - 1]
72
- end
73
- for i in (ary.size + 1)..@worksheet.num_cols
74
- @worksheet[1, i] = ""
75
- end
76
- end
77
-
78
- # Adds a new row to the bottom.
79
- # Keys of +hash+ are colum names (the first row).
80
- # Returns GoogleDriveV0::ListRow for the new row.
81
- #
82
- # Note that update is not sent to the server until
83
- # you call GoogleDriveV0::Worksheet#save().
84
- def push(hash)
85
- row = self[self.size]
86
- row.update(hash)
87
- return row
88
- end
89
-
90
- # Returns all rows (except for the first row) as Array of Hash.
91
- # Keys of Hash objects are colum names (the first row).
92
- def to_hash_array()
93
- return self.map(){ |r| r.to_hash() }
94
- end
95
-
96
- def get(index, key) #:nodoc:
97
- return @worksheet[index + 2, key_to_col(key)]
98
- end
99
-
100
- def numeric_value(index, key) #:nodoc:
101
- return @worksheet.numeric_value(index + 2, key_to_col(key))
102
- end
103
-
104
- def set(index, key, value) #:nodoc:
105
- @worksheet[index + 2, key_to_col(key)] = value
106
- end
107
-
108
- private
109
-
110
- def key_to_col(key)
111
- key = key.to_s()
112
- col = (1..@worksheet.num_cols).find(){ |c| @worksheet[1, c] == key }
113
- raise(GoogleDriveV0::Error, "Column doesn't exist: %p" % key) if !col
114
- return col
115
- end
116
-
117
- end
118
-
119
- end
@@ -1,88 +0,0 @@
1
- # Author: Hiroshi Ichikawa <http://gimite.net/>
2
- # The license of this source is "New BSD Licence"
3
-
4
- require "forwardable"
5
-
6
- require "google_drive_v0/util"
7
- require "google_drive_v0/error"
8
-
9
-
10
- module GoogleDriveV0
11
-
12
- # Hash-like object returned by GoogleDriveV0::List#[].
13
- class ListRow
14
-
15
- include(Enumerable)
16
- extend(Forwardable)
17
-
18
- def_delegators(:to_hash,
19
- :keys, :values, :each_key, :each_value, :each, :each_pair, :hash,
20
- :assoc, :fetch, :flatten, :key, :invert, :size, :length, :rassoc,
21
- :merge, :reject, :select, :sort, :to_a, :values_at)
22
-
23
- def initialize(list, index) #:nodoc:
24
- @list = list
25
- @index = index
26
- end
27
-
28
- def [](key)
29
- return @list.get(@index, key)
30
- end
31
-
32
- def numeric_value(key)
33
- return @list.numeric_value(@index, key)
34
- end
35
-
36
- def []=(key, value)
37
- @list.set(@index, key, value)
38
- end
39
-
40
- def has_key?(key)
41
- return @list.keys.include?(key)
42
- end
43
-
44
- alias include? has_key?
45
- alias key? has_key?
46
- alias member? has_key?
47
-
48
- def update(hash)
49
- for k, v in hash
50
- self[k] = v
51
- end
52
- end
53
-
54
- alias merge! update
55
-
56
- def replace(hash)
57
- clear()
58
- update(hash)
59
- end
60
-
61
- def clear()
62
- for key in @list.keys
63
- self[key] = ""
64
- end
65
- end
66
-
67
- def to_hash()
68
- result = {}
69
- for key in @list.keys
70
- result[key] = self[key]
71
- end
72
- return result
73
- end
74
-
75
- def ==(other)
76
- return self.class == other.class && self.to_hash() == other.to_hash()
77
- end
78
-
79
- alias === ==
80
- alias eql? ==
81
-
82
- def inspect
83
- return "\#<%p %p>" % [self.class, to_hash()]
84
- end
85
-
86
- end
87
-
88
- end
@@ -1,26 +0,0 @@
1
- # Author: Hiroshi Ichikawa <http://gimite.net/>
2
- # The license of this source is "New BSD Licence"
3
-
4
- require "rubygems"
5
- require "oauth"
6
-
7
-
8
- module GoogleDriveV0
9
-
10
- class OAuth1Fetcher #:nodoc:
11
-
12
- def initialize(oauth1_token)
13
- @oauth1_token = oauth1_token
14
- end
15
-
16
- def request_raw(method, url, data, extra_header, auth)
17
- if method == :delete || method == :get
18
- return @oauth1_token.__send__(method, url, extra_header)
19
- else
20
- return @oauth1_token.__send__(method, url, data, extra_header)
21
- end
22
- end
23
-
24
- end
25
-
26
- end
@@ -1,24 +0,0 @@
1
- # Author: Hiroshi Ichikawa <http://gimite.net/>
2
- # The license of this source is "New BSD Licence"
3
-
4
- require "google_drive_v0/basic_fetcher"
5
-
6
-
7
- module GoogleDriveV0
8
-
9
- class OAuth2Fetcher < BasicFetcher #:nodoc:
10
-
11
- def initialize(auth_token, proxy)
12
- super(proxy)
13
- @auth_token = auth_token
14
- end
15
-
16
- private
17
-
18
- def auth_header(auth)
19
- return {"Authorization" => "Bearer %s" % @auth_token}
20
- end
21
-
22
- end
23
-
24
- end
@@ -1,31 +0,0 @@
1
- # Author: Hiroshi Ichikawa <http://gimite.net/>
2
- # The license of this source is "New BSD Licence"
3
-
4
- require "google_drive_v0/util"
5
- require "google_drive_v0/error"
6
-
7
-
8
- module GoogleDriveV0
9
-
10
- # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
11
- # March 2012.
12
- #
13
- # Use GoogleDriveV0::Table#records to get GoogleDriveV0::Record objects.
14
- class Record < Hash
15
- include(Util)
16
-
17
- def initialize(session, entry) #:nodoc:
18
- @session = session
19
- entry.css("gs|field").each() do |field|
20
- self[field["name"]] = field.inner_text
21
- end
22
- end
23
-
24
- def inspect #:nodoc:
25
- content = self.map(){ |k, v| "%p => %p" % [k, v] }.join(", ")
26
- return "\#<%p:{%s}>" % [self.class, content]
27
- end
28
-
29
- end
30
-
31
- end
@@ -1,522 +0,0 @@
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 "oauth"
10
- require "oauth2"
11
-
12
- require "google_drive_v0/util"
13
- require "google_drive_v0/client_login_fetcher"
14
- require "google_drive_v0/oauth1_fetcher"
15
- require "google_drive_v0/oauth2_fetcher"
16
- require "google_drive_v0/error"
17
- require "google_drive_v0/authentication_error"
18
- require "google_drive_v0/spreadsheet"
19
- require "google_drive_v0/worksheet"
20
- require "google_drive_v0/collection"
21
- require "google_drive_v0/file"
22
-
23
-
24
- module GoogleDriveV0
25
-
26
- # Use GoogleDriveV0.login or GoogleDriveV0.saved_session to get
27
- # GoogleDriveV0::Session object.
28
- class Session
29
-
30
- include(Util)
31
- extend(Util)
32
-
33
- UPLOAD_CHUNK_SIZE = 512 * 1024
34
-
35
- # DEPRECATED: Will be removed in the next version.
36
- #
37
- # The same as GoogleDriveV0.login.
38
- def self.login(mail, password, proxy = nil)
39
- warn(
40
- "WARNING: GoogleDriveV0.login is deprecated and will be removed in the next version. " +
41
- "Use GoogleDriveV0.login_with_oauth instead.")
42
- session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
43
- session.login(mail, password)
44
- return session
45
- end
46
-
47
- # The same as GoogleDriveV0.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(GoogleDriveV0::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(GoogleDriveV0::Error,
67
- "access_token is neither String, OAuth2::Token nor OAuth::Token: %p" % access_token)
68
- end
69
- return Session.new(nil, fetcher)
70
- end
71
-
72
- # The same as GoogleDriveV0.restore_session.
73
- def self.restore_session(auth_tokens, proxy = nil)
74
- warn(
75
- "WARNING: GoogleDriveV0.restore_session is deprecated and will be removed in the next version. " +
76
- "Use GoogleDriveV0.login_with_oauth instead.")
77
- return Session.new(auth_tokens, nil, proxy)
78
- end
79
-
80
- # Creates a dummy GoogleDriveV0::Session object for testing.
81
- def self.new_dummy()
82
- return Session.new(nil, Object.new())
83
- end
84
-
85
- # DEPRECATED: Use GoogleDriveV0.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
93
-
94
- # Authenticates with given +mail+ and +password+, and updates current session object
95
- # if succeeds. Raises GoogleDriveV0::AuthenticationError if fails.
96
- # Google Apps account is supported.
97
- def login(mail, password)
98
- if !@fetcher.is_a?(ClientLoginFetcher)
99
- raise(GoogleDriveV0::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 GoogleDriveV0::Error => ex
108
- return true if @on_auth_fail && @on_auth_fail.call()
109
- raise(AuthenticationError, "Authentication failed for #{mail}: #{ex.message}")
110
- end
111
- end
112
-
113
- # Authentication tokens.
114
- def auth_tokens
115
- warn(
116
- "WARNING: GoogleDriveV0::Session\#auth_tokens is deprecated and will be removed in the next version.")
117
- if !@fetcher.is_a?(ClientLoginFetcher)
118
- raise(GoogleDriveV0::Error,
119
- "Cannot call auth_tokens for session created by " +
120
- "login_with_oauth.")
121
- end
122
- return @fetcher.auth_tokens
123
- end
124
-
125
- # Authentication token.
126
- def auth_token(auth = :wise)
127
- warn(
128
- "WARNING: GoogleDriveV0::Session\#auth_token is deprecated and will be removed in the next version.")
129
- return self.auth_tokens[auth]
130
- end
131
-
132
- # Proc or Method called when authentication has failed.
133
- # When this function returns +true+, it tries again.
134
- def on_auth_fail
135
- warn(
136
- "WARNING: GoogleDriveV0::Session\#on_auth_fail is deprecated and will be removed in the next version.")
137
- return @on_auth_fail
138
- end
139
-
140
- def on_auth_fail=(func)
141
- warn(
142
- "WARNING: GoogleDriveV0::Session\#on_auth_fail is deprecated and will be removed in the next version.")
143
- @on_auth_fail = func
144
- end
145
-
146
- # Returns list of files for the user as array of GoogleDriveV0::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.
151
- #
152
- # e.g.
153
- # 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) }
160
- end
161
-
162
- # Returns GoogleDriveV0::File or its subclass whose title exactly matches +title+.
163
- # Returns nil if not found. If multiple files with the +title+ are found, returns
164
- # one of them.
165
- #
166
- # If given an Array, traverses collections by title. e.g.
167
- # session.file_by_title(["myfolder", "mysubfolder/even/w/slash", "myfile"])
168
- def file_by_title(title)
169
- if title.is_a?(Array)
170
- return self.root_collection.file_by_title(title)
171
- else
172
- return files("title" => title, "title-exact" => "true")[0]
173
- end
174
- end
175
-
176
- # Returns list of spreadsheets for the user as array of GoogleDriveV0::Spreadsheet.
177
- # You can specify query parameters e.g. "title", "title-exact".
178
- #
179
- # e.g.
180
- # 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) }
192
- end
193
-
194
- # Returns GoogleDriveV0::Spreadsheet with given +key+.
195
- #
196
- # e.g.
197
- # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
198
- # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
199
- def spreadsheet_by_key(key)
200
- url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
201
- return Spreadsheet.new(self, url)
202
- end
203
-
204
- # Returns GoogleDriveV0::Spreadsheet with given +url+. You must specify either of:
205
- # - URL of the page you open to access the spreadsheet in your browser
206
- # - URL of worksheet-based feed of the spreadseet
207
- #
208
- # e.g.
209
- # session.spreadsheet_by_url(
210
- # "https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg")
211
- # session.spreadsheet_by_url(
212
- # "https://spreadsheets.google.com/feeds/" +
213
- # "worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
214
- 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
226
- end
227
- # Assumes the URL is worksheets feed URL.
228
- return Spreadsheet.new(self, url)
229
- end
230
-
231
- # Returns GoogleDriveV0::Spreadsheet with given +title+.
232
- # Returns nil if not found. If multiple spreadsheets with the +title+ are found, returns
233
- # one of them.
234
- def spreadsheet_by_title(title)
235
- return spreadsheets({"title" => title, "title-exact" => "true"})[0]
236
- end
237
-
238
- # Returns GoogleDriveV0::Worksheet with given +url+.
239
- # You must specify URL of cell-based feed of the worksheet.
240
- #
241
- # e.g.
242
- # session.worksheet_by_url(
243
- # "http://spreadsheets.google.com/feeds/" +
244
- # "cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
245
- def worksheet_by_url(url)
246
- return Worksheet.new(self, nil, url)
247
- end
248
-
249
- # Returns the root collection.
250
- def root_collection
251
- return Collection.new(self, Collection::ROOT_URL)
252
- end
253
-
254
- # Returns the top-level collections (direct children of the root collection).
255
- def collections
256
- return self.root_collection.subcollections
257
- end
258
-
259
- # Returns a top-level collection whose title exactly matches +title+ as
260
- # GoogleDriveV0::Collection.
261
- # Returns nil if not found. If multiple collections with the +title+ are found, returns
262
- # one of them.
263
- def collection_by_title(title)
264
- return self.root_collection.subcollection_by_title(title)
265
- end
266
-
267
- # Returns GoogleDriveV0::Collection with given +url+.
268
- # You must specify either of:
269
- # - URL of the page you get when you go to https://docs.google.com/ with your browser and
270
- # open a collection
271
- # - URL of collection (folder) feed
272
- #
273
- # e.g.
274
- # session.collection_by_url(
275
- # "https://drive.google.com/#folders/" +
276
- # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
277
- # session.collection_by_url(
278
- # "http://docs.google.com/feeds/default/private/full/folder%3A" +
279
- # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
280
- 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}"
286
- end
287
- return Collection.new(self, to_v3_url(url))
288
- end
289
-
290
- # Creates new spreadsheet and returns the new GoogleDriveV0::Spreadsheet.
291
- #
292
- # e.g.
293
- # 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
-
315
- end
316
-
317
- # Uploads a file with the given +title+ and +content+.
318
- # Returns a GoogleSpreadsheet::File object.
319
- #
320
- # e.g.
321
- # # Uploads and converts to a Google Docs document:
322
- # session.upload_from_string(
323
- # "Hello world.", "Hello", :content_type => "text/plain")
324
- #
325
- # # Uploads without conversion:
326
- # session.upload_from_string(
327
- # "Hello world.", "Hello", :content_type => "text/plain", :convert => false)
328
- #
329
- # # Uploads and converts to a Google Spreadsheet:
330
- # session.upload_from_string("hoge\tfoo\n", "Hoge", :content_type => "text/tab-separated-values")
331
- # session.upload_from_string("hoge,foo\n", "Hoge", :content_type => "text/tsv")
332
- def upload_from_string(content, title = "Untitled", params = {})
333
- return upload_from_io(StringIO.new(content), title, params)
334
- end
335
-
336
- # Uploads a local file.
337
- # Returns a GoogleSpreadsheet::File object.
338
- #
339
- # e.g.
340
- # # Uploads a text file and converts to a Google Docs document:
341
- # session.upload_from_file("/path/to/hoge.txt")
342
- #
343
- # # Uploads without conversion:
344
- # session.upload_from_file("/path/to/hoge.txt", "Hoge", :convert => false)
345
- #
346
- # # Uploads with explicit content type:
347
- # session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/plain")
348
- #
349
- # # Uploads a text file and converts to a Google Spreadsheet:
350
- # session.upload_from_file("/path/to/hoge.tsv", "Hoge")
351
- # 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
- # session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/csv")
354
- def upload_from_file(path, title = nil, params = {})
355
- file_name = ::File.basename(path)
356
- 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
360
- end
361
-
362
- # Uploads a file. Reads content from +io+.
363
- # Returns a GoogleSpreadsheet::File object.
364
- 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)
371
- end
372
-
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"
386
- 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
423
- end
424
- else
425
- upload_header = {
426
- "Content-Type" => content_type,
427
- }
428
- doc = request(
429
- :put, upload_url, :header => upload_header, :data => "", :auth => :writely)
430
- end
431
-
432
- return doc.root
433
-
434
- end
435
-
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 GoogleDriveV0::File.new(self, entry)
448
- end
449
- end
450
-
451
- def request(method, url, params = {}) #:nodoc:
452
-
453
- # Always uses HTTPS.
454
- url = url.gsub(%r{^http://}, "https://")
455
- data = params[:data]
456
- auth = params[:auth] || :wise
457
- if params[:header]
458
- extra_header = params[:header]
459
- elsif data
460
- extra_header = {"Content-Type" => "application/atom+xml;charset=utf-8"}
461
- else
462
- extra_header = {}
463
- end
464
- response_type = params[:response_type] || :xml
465
-
466
- while true
467
- response = @fetcher.request_raw(method, url, data, extra_header, auth)
468
- if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
469
- next
470
- end
471
- if !(response.code =~ /^[23]/)
472
- raise(
473
- response.code == "401" ? AuthenticationError : GoogleDriveV0::Error,
474
- "Response code #{response.code} for #{method} #{url}: " +
475
- CGI.unescapeHTML(response.body))
476
- end
477
- return convert_response(response, response_type)
478
- end
479
-
480
- end
481
-
482
- def inspect
483
- return "#<%p:0x%x>" % [self.class, self.object_id]
484
- end
485
-
486
- private
487
-
488
- def convert_response(response, response_type)
489
- case response_type
490
- when :xml
491
- return Nokogiri.XML(response.body)
492
- when :raw
493
- return response.body
494
- when :response
495
- return response
496
- else
497
- raise(GoogleDriveV0::Error,
498
- "Unknown params[:response_type]: %s" % response_type)
499
- end
500
- end
501
-
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)
518
- end
519
-
520
- end
521
-
522
- end