google_drive 1.0.4 → 1.0.5

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