google_drive 0.3.0
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.
- data/README.rdoc +89 -0
- data/doc_src/google_drive/acl.rb +20 -0
- data/doc_src/google_drive/acl_entry.rb +33 -0
- data/lib/google_drive.rb +120 -0
- data/lib/google_drive/acl.rb +124 -0
- data/lib/google_drive/acl_entry.rb +58 -0
- data/lib/google_drive/authentication_error.rb +14 -0
- data/lib/google_drive/client_login_fetcher.rb +56 -0
- data/lib/google_drive/collection.rb +54 -0
- data/lib/google_drive/error.rb +12 -0
- data/lib/google_drive/file.rb +217 -0
- data/lib/google_drive/list.rb +115 -0
- data/lib/google_drive/list_row.rb +84 -0
- data/lib/google_drive/oauth1_fetcher.rb +26 -0
- data/lib/google_drive/oauth2_fetcher.rb +47 -0
- data/lib/google_drive/record.rb +31 -0
- data/lib/google_drive/session.rb +436 -0
- data/lib/google_drive/spreadsheet.rb +220 -0
- data/lib/google_drive/table.rb +60 -0
- data/lib/google_drive/util.rb +55 -0
- data/lib/google_drive/worksheet.rb +445 -0
- metadata +121 -0
@@ -0,0 +1,84 @@
|
|
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/util"
|
7
|
+
require "google_drive/error"
|
8
|
+
|
9
|
+
|
10
|
+
module GoogleDrive
|
11
|
+
|
12
|
+
# Hash-like object returned by GoogleDrive::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 []=(key, value)
|
33
|
+
@list.set(@index, key, value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_key?(key)
|
37
|
+
return @list.keys.include?(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias include? has_key?
|
41
|
+
alias key? has_key?
|
42
|
+
alias member? has_key?
|
43
|
+
|
44
|
+
def update(hash)
|
45
|
+
for k, v in hash
|
46
|
+
self[k] = v
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias merge! update
|
51
|
+
|
52
|
+
def replace(hash)
|
53
|
+
clear()
|
54
|
+
update(hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear()
|
58
|
+
for key in @list.keys
|
59
|
+
self[key] = ""
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_hash()
|
64
|
+
result = {}
|
65
|
+
for key in @list.keys
|
66
|
+
result[key] = self[key]
|
67
|
+
end
|
68
|
+
return result
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
return self.class == other.class && self.to_hash() == other.to_hash()
|
73
|
+
end
|
74
|
+
|
75
|
+
alias === ==
|
76
|
+
alias eql? ==
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
return "\#<%p %p>" % [self.class, to_hash()]
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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 GoogleDrive
|
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
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Author: Hiroshi Ichikawa <http://gimite.net/>
|
2
|
+
# The license of this source is "New BSD Licence"
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "oauth2"
|
6
|
+
|
7
|
+
|
8
|
+
module GoogleDrive
|
9
|
+
|
10
|
+
class OAuth2Fetcher #:nodoc:
|
11
|
+
|
12
|
+
class Response
|
13
|
+
|
14
|
+
def initialize(raw_res)
|
15
|
+
@raw_res = raw_res
|
16
|
+
end
|
17
|
+
|
18
|
+
def code
|
19
|
+
return @raw_res.status.to_s()
|
20
|
+
end
|
21
|
+
|
22
|
+
def body
|
23
|
+
return @raw_res.body
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](name)
|
27
|
+
return @raw_res.headers[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(oauth2_token)
|
33
|
+
@oauth2_token = oauth2_token
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_raw(method, url, data, extra_header, auth)
|
37
|
+
if method == :delete || method == :get
|
38
|
+
raw_res = @oauth2_token.request(method, url, {:header => extra_header})
|
39
|
+
else
|
40
|
+
raw_res = @oauth2_token.request(method, url, {:header => extra_header, :body => data})
|
41
|
+
end
|
42
|
+
return Response.new(raw_res)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Author: Hiroshi Ichikawa <http://gimite.net/>
|
2
|
+
# The license of this source is "New BSD Licence"
|
3
|
+
|
4
|
+
require "google_drive/util"
|
5
|
+
require "google_drive/error"
|
6
|
+
|
7
|
+
|
8
|
+
module GoogleDrive
|
9
|
+
|
10
|
+
# DEPRECATED: Table and Record feeds are deprecated and they will not be available after
|
11
|
+
# March 2012.
|
12
|
+
#
|
13
|
+
# Use GoogleDrive::Table#records to get GoogleDrive::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
|
@@ -0,0 +1,436 @@
|
|
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
|
+
|
10
|
+
require "google_drive/util"
|
11
|
+
require "google_drive/client_login_fetcher"
|
12
|
+
require "google_drive/oauth1_fetcher"
|
13
|
+
require "google_drive/oauth2_fetcher"
|
14
|
+
require "google_drive/error"
|
15
|
+
require "google_drive/authentication_error"
|
16
|
+
require "google_drive/spreadsheet"
|
17
|
+
require "google_drive/worksheet"
|
18
|
+
require "google_drive/collection"
|
19
|
+
require "google_drive/file"
|
20
|
+
|
21
|
+
|
22
|
+
module GoogleDrive
|
23
|
+
|
24
|
+
# Use GoogleDrive.login or GoogleDrive.saved_session to get
|
25
|
+
# GoogleDrive::Session object.
|
26
|
+
class Session
|
27
|
+
|
28
|
+
include(Util)
|
29
|
+
extend(Util)
|
30
|
+
|
31
|
+
UPLOAD_CHUNK_SIZE = 512 * 1024
|
32
|
+
|
33
|
+
# The same as GoogleDrive.login.
|
34
|
+
def self.login(mail, password, proxy = nil)
|
35
|
+
session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
|
36
|
+
session.login(mail, password)
|
37
|
+
return session
|
38
|
+
end
|
39
|
+
|
40
|
+
# The same as GoogleDrive.login_with_oauth.
|
41
|
+
def self.login_with_oauth(oauth_token)
|
42
|
+
case oauth_token
|
43
|
+
when OAuth::AccessToken
|
44
|
+
fetcher = OAuth1Fetcher.new(oauth_token)
|
45
|
+
when OAuth2::AccessToken
|
46
|
+
fetcher = OAuth2Fetcher.new(oauth_token)
|
47
|
+
else
|
48
|
+
raise(GoogleDrive::Error,
|
49
|
+
"oauth_token is neither OAuth::Token nor OAuth2::Token: %p" % oauth_token)
|
50
|
+
end
|
51
|
+
return Session.new(nil, fetcher)
|
52
|
+
end
|
53
|
+
|
54
|
+
# The same as GoogleDrive.restore_session.
|
55
|
+
def self.restore_session(auth_tokens, proxy = nil)
|
56
|
+
return Session.new(auth_tokens, nil, proxy)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a dummy GoogleDrive::Session object for testing.
|
60
|
+
def self.new_dummy()
|
61
|
+
return Session.new(nil, Object.new())
|
62
|
+
end
|
63
|
+
|
64
|
+
# DEPRECATED: Use GoogleDrive.restore_session instead.
|
65
|
+
def initialize(auth_tokens = nil, fetcher = nil, proxy = nil)
|
66
|
+
if fetcher
|
67
|
+
@fetcher = fetcher
|
68
|
+
else
|
69
|
+
@fetcher = ClientLoginFetcher.new(auth_tokens || {}, proxy)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Authenticates with given +mail+ and +password+, and updates current session object
|
74
|
+
# if succeeds. Raises GoogleDrive::AuthenticationError if fails.
|
75
|
+
# Google Apps account is supported.
|
76
|
+
def login(mail, password)
|
77
|
+
if !@fetcher.is_a?(ClientLoginFetcher)
|
78
|
+
raise(GoogleDrive::Error,
|
79
|
+
"Cannot call login for session created by login_with_oauth.")
|
80
|
+
end
|
81
|
+
begin
|
82
|
+
@fetcher.auth_tokens = {
|
83
|
+
:wise => authenticate(mail, password, :wise),
|
84
|
+
:writely => authenticate(mail, password, :writely),
|
85
|
+
}
|
86
|
+
rescue GoogleDrive::Error => ex
|
87
|
+
return true if @on_auth_fail && @on_auth_fail.call()
|
88
|
+
raise(AuthenticationError, "Authentication failed for #{mail}: #{ex.message}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Authentication tokens.
|
93
|
+
def auth_tokens
|
94
|
+
if !@fetcher.is_a?(ClientLoginFetcher)
|
95
|
+
raise(GoogleDrive::Error,
|
96
|
+
"Cannot call auth_tokens for session created by " +
|
97
|
+
"login_with_oauth.")
|
98
|
+
end
|
99
|
+
return @fetcher.auth_tokens
|
100
|
+
end
|
101
|
+
|
102
|
+
# Authentication token.
|
103
|
+
def auth_token(auth = :wise)
|
104
|
+
return self.auth_tokens[auth]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Proc or Method called when authentication has failed.
|
108
|
+
# When this function returns +true+, it tries again.
|
109
|
+
attr_accessor :on_auth_fail
|
110
|
+
|
111
|
+
# Returns list of files for the user as array of GoogleDrive::File or its subclass.
|
112
|
+
# You can specify query parameters described at
|
113
|
+
# https://developers.google.com/google-apps/documents-list/#getting_a_list_of_documents_and_files
|
114
|
+
#
|
115
|
+
# e.g.
|
116
|
+
# session.files
|
117
|
+
# session.files("title" => "hoge", "title-exact" => "true")
|
118
|
+
def files(params = {})
|
119
|
+
url = concat_url(
|
120
|
+
"https://docs.google.com/feeds/default/private/full?v=3", "?" + encode_query(params))
|
121
|
+
doc = request(:get, url, :auth => :writely)
|
122
|
+
return doc.css("feed > entry").map(){ |e| entry_element_to_file(e) }
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns GoogleDrive::File or its subclass whose title exactly matches +title+.
|
126
|
+
# Returns nil if not found. If multiple files with the +title+ are found, returns
|
127
|
+
# one of them.
|
128
|
+
def file_by_title(title)
|
129
|
+
return files("title" => title, "title-exact" => "true")[0]
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns list of spreadsheets for the user as array of GoogleDrive::Spreadsheet.
|
133
|
+
# You can specify query parameters described at
|
134
|
+
# http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
|
135
|
+
#
|
136
|
+
# e.g.
|
137
|
+
# session.spreadsheets
|
138
|
+
# session.spreadsheets("title" => "hoge")
|
139
|
+
def spreadsheets(params = {})
|
140
|
+
query = encode_query(params)
|
141
|
+
doc = request(
|
142
|
+
:get, "https://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
|
143
|
+
result = []
|
144
|
+
doc.css("feed > entry").each() do |entry|
|
145
|
+
title = entry.css("title").text
|
146
|
+
url = entry.css(
|
147
|
+
"link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
|
148
|
+
result.push(Spreadsheet.new(self, url, title))
|
149
|
+
end
|
150
|
+
return result
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns GoogleDrive::Spreadsheet with given +key+.
|
154
|
+
#
|
155
|
+
# e.g.
|
156
|
+
# # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
|
157
|
+
# session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
|
158
|
+
def spreadsheet_by_key(key)
|
159
|
+
url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
|
160
|
+
return Spreadsheet.new(self, url)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns GoogleDrive::Spreadsheet with given +url+. You must specify either of:
|
164
|
+
# - URL of the page you open to access the spreadsheet in your browser
|
165
|
+
# - URL of worksheet-based feed of the spreadseet
|
166
|
+
#
|
167
|
+
# e.g.
|
168
|
+
# session.spreadsheet_by_url(
|
169
|
+
# "https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg")
|
170
|
+
# session.spreadsheet_by_url(
|
171
|
+
# "https://spreadsheets.google.com/feeds/" +
|
172
|
+
# "worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
|
173
|
+
def spreadsheet_by_url(url)
|
174
|
+
# Tries to parse it as URL of human-readable spreadsheet.
|
175
|
+
uri = URI.parse(url)
|
176
|
+
if ["spreadsheets.google.com", "docs.google.com"].include?(uri.host) &&
|
177
|
+
uri.path =~ /\/ccc$/
|
178
|
+
if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
|
179
|
+
return spreadsheet_by_key($1)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
# Assumes the URL is worksheets feed URL.
|
183
|
+
return Spreadsheet.new(self, url)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns GoogleDrive::Spreadsheet with given +title+.
|
187
|
+
# Returns nil if not found. If multiple spreadsheets with the +title+ are found, returns
|
188
|
+
# one of them.
|
189
|
+
def spreadsheet_by_title(title)
|
190
|
+
return spreadsheets({"title" => title})[0]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns GoogleDrive::Worksheet with given +url+.
|
194
|
+
# You must specify URL of cell-based feed of the worksheet.
|
195
|
+
#
|
196
|
+
# e.g.
|
197
|
+
# session.worksheet_by_url(
|
198
|
+
# "http://spreadsheets.google.com/feeds/" +
|
199
|
+
# "cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
|
200
|
+
def worksheet_by_url(url)
|
201
|
+
return Worksheet.new(self, nil, url)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns GoogleDrive::Collection with given +url+.
|
205
|
+
# You must specify either of:
|
206
|
+
# - URL of the page you get when you go to https://docs.google.com/ with your browser and
|
207
|
+
# open a collection
|
208
|
+
# - URL of collection (folder) feed
|
209
|
+
#
|
210
|
+
# e.g.
|
211
|
+
# session.collection_by_url(
|
212
|
+
# "https://drive.google.com/#folders/" +
|
213
|
+
# "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
|
214
|
+
# session.collection_by_url(
|
215
|
+
# "http://docs.google.com/feeds/default/private/full/folder%3A" +
|
216
|
+
# "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
|
217
|
+
def collection_by_url(url)
|
218
|
+
uri = URI.parse(url)
|
219
|
+
if ["docs.google.com", "drive.google.com"].include?(uri.host) &&
|
220
|
+
uri.fragment =~ /^folders\/(.+)$/
|
221
|
+
# Looks like a URL of human-readable collection page. Converts to collection feed URL.
|
222
|
+
url = "https://docs.google.com/feeds/default/private/full/folder%3A#{$1}"
|
223
|
+
end
|
224
|
+
return Collection.new(self, url)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Creates new spreadsheet and returns the new GoogleDrive::Spreadsheet.
|
228
|
+
#
|
229
|
+
# e.g.
|
230
|
+
# session.create_spreadsheet("My new sheet")
|
231
|
+
def create_spreadsheet(
|
232
|
+
title = "Untitled",
|
233
|
+
feed_url = "https://docs.google.com/feeds/documents/private/full")
|
234
|
+
|
235
|
+
xml = <<-"EOS"
|
236
|
+
<atom:entry
|
237
|
+
xmlns:atom="http://www.w3.org/2005/Atom"
|
238
|
+
xmlns:docs="http://schemas.google.com/docs/2007">
|
239
|
+
<atom:category
|
240
|
+
scheme="http://schemas.google.com/g/2005#kind"
|
241
|
+
term="http://schemas.google.com/docs/2007#spreadsheet"
|
242
|
+
label="spreadsheet"/>
|
243
|
+
<atom:title>#{h(title)}</atom:title>
|
244
|
+
</atom:entry>
|
245
|
+
EOS
|
246
|
+
|
247
|
+
doc = request(:post, feed_url, :data => xml, :auth => :writely)
|
248
|
+
ss_url = doc.css(
|
249
|
+
"link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
|
250
|
+
return Spreadsheet.new(self, ss_url, title)
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
# Uploads a file with the given +title+ and +content+.
|
255
|
+
# Returns a GoogleSpreadsheet::File object.
|
256
|
+
#
|
257
|
+
# e.g.
|
258
|
+
# # Uploads and converts to a Google Docs document:
|
259
|
+
# session.upload_from_string(
|
260
|
+
# "Hello world.", "Hello", :content_type => "text/plain")
|
261
|
+
#
|
262
|
+
# # Uploads without conversion:
|
263
|
+
# session.upload_from_string(
|
264
|
+
# "Hello world.", "Hello", :content_type => "text/plain", :convert => false)
|
265
|
+
def upload_from_string(content, title = "Untitled", params = {})
|
266
|
+
return upload_from_io(StringIO.new(content), title, params)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Uploads a local file.
|
270
|
+
# Returns a GoogleSpreadsheet::File object.
|
271
|
+
#
|
272
|
+
# e.g.
|
273
|
+
# # Uploads and converts to a Google Docs document:
|
274
|
+
# session.upload_from_file("/path/to/hoge.txt")
|
275
|
+
#
|
276
|
+
# # Uploads without conversion:
|
277
|
+
# session.upload_from_file("/path/to/hoge.txt", "Hoge", :convert => false)
|
278
|
+
#
|
279
|
+
# # Uploads with explicit content type:
|
280
|
+
# session.upload_from_file("/path/to/hoge", "Hoge", :content_type => "text/plain")
|
281
|
+
def upload_from_file(path, title = nil, params = {})
|
282
|
+
file_name = ::File.basename(path)
|
283
|
+
params = {:file_name => file_name}.merge(params)
|
284
|
+
open(path, "rb") do |f|
|
285
|
+
return upload_from_io(f, title || file_name, params)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Uploads a file. Reads content from +io+.
|
290
|
+
# Returns a GoogleSpreadsheet::File object.
|
291
|
+
def upload_from_io(io, title = "Untitled", params = {})
|
292
|
+
doc = request(:get, "https://docs.google.com/feeds/default/private/full?v=3",
|
293
|
+
:auth => :writely)
|
294
|
+
initial_url = doc.css(
|
295
|
+
"link[rel='http://schemas.google.com/g/2005#resumable-create-media']")[0]["href"]
|
296
|
+
return upload_raw(:post, initial_url, io, title, params)
|
297
|
+
end
|
298
|
+
|
299
|
+
def upload_raw(method, url, io, title = "Untitled", params = {}) #:nodoc:
|
300
|
+
|
301
|
+
params = {:convert => true}.merge(params)
|
302
|
+
pos = io.pos
|
303
|
+
io.seek(0, IO::SEEK_END)
|
304
|
+
total_bytes = io.pos - pos
|
305
|
+
io.pos = pos
|
306
|
+
content_type = params[:content_type]
|
307
|
+
if !content_type && params[:file_name]
|
308
|
+
content_type = EXT_TO_CONTENT_TYPE[::File.extname(params[:file_name]).downcase]
|
309
|
+
end
|
310
|
+
if !content_type
|
311
|
+
content_type = "application/octet-stream"
|
312
|
+
end
|
313
|
+
|
314
|
+
initial_xml = <<-"EOS"
|
315
|
+
<entry xmlns="http://www.w3.org/2005/Atom"
|
316
|
+
xmlns:docs="http://schemas.google.com/docs/2007">
|
317
|
+
<title>#{h(title)}</title>
|
318
|
+
</entry>
|
319
|
+
EOS
|
320
|
+
|
321
|
+
default_initial_header = {
|
322
|
+
"Content-Type" => "application/atom+xml",
|
323
|
+
"X-Upload-Content-Type" => content_type,
|
324
|
+
"X-Upload-Content-Length" => total_bytes.to_s(),
|
325
|
+
}
|
326
|
+
initial_full_url = concat_url(url, params[:convert] ? "?convert=true" : "?convert=false")
|
327
|
+
initial_response = request(method, initial_full_url,
|
328
|
+
:header => default_initial_header.merge(params[:header] || {}),
|
329
|
+
:data => initial_xml,
|
330
|
+
:auth => :writely,
|
331
|
+
:response_type => :response)
|
332
|
+
upload_url = initial_response["location"]
|
333
|
+
|
334
|
+
sent_bytes = 0
|
335
|
+
while data = io.read(UPLOAD_CHUNK_SIZE)
|
336
|
+
content_range = "bytes %d-%d/%d" % [
|
337
|
+
sent_bytes,
|
338
|
+
sent_bytes + data.bytesize - 1,
|
339
|
+
total_bytes,
|
340
|
+
]
|
341
|
+
upload_header = {
|
342
|
+
"Content-Type" => content_type,
|
343
|
+
"Content-Range" => content_range,
|
344
|
+
}
|
345
|
+
doc = request(
|
346
|
+
:put, upload_url, :header => upload_header, :data => data, :auth => :writely)
|
347
|
+
sent_bytes += data.bytesize
|
348
|
+
end
|
349
|
+
|
350
|
+
return entry_element_to_file(doc.root)
|
351
|
+
|
352
|
+
end
|
353
|
+
|
354
|
+
def entry_element_to_file(entry) #:nodoc:
|
355
|
+
title = entry.css("title").text
|
356
|
+
worksheets_feed_link = entry.css(
|
357
|
+
"link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]
|
358
|
+
if worksheets_feed_link
|
359
|
+
return Spreadsheet.new(self, worksheets_feed_link["href"], title)
|
360
|
+
else
|
361
|
+
return GoogleDrive::File.new(self, entry)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def request(method, url, params = {}) #:nodoc:
|
366
|
+
|
367
|
+
# Always uses HTTPS.
|
368
|
+
url = url.gsub(%r{^http://}, "https://")
|
369
|
+
data = params[:data]
|
370
|
+
auth = params[:auth] || :wise
|
371
|
+
if params[:header]
|
372
|
+
extra_header = params[:header]
|
373
|
+
elsif data
|
374
|
+
extra_header = {"Content-Type" => "application/atom+xml"}
|
375
|
+
else
|
376
|
+
extra_header = {}
|
377
|
+
end
|
378
|
+
response_type = params[:response_type] || :xml
|
379
|
+
|
380
|
+
while true
|
381
|
+
response = @fetcher.request_raw(method, url, data, extra_header, auth)
|
382
|
+
if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
|
383
|
+
next
|
384
|
+
end
|
385
|
+
if !(response.code =~ /^[23]/)
|
386
|
+
raise(
|
387
|
+
response.code == "401" ? AuthenticationError : GoogleDrive::Error,
|
388
|
+
"Response code #{response.code} for #{method} #{url}: " +
|
389
|
+
CGI.unescapeHTML(response.body))
|
390
|
+
end
|
391
|
+
return convert_response(response, response_type)
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
|
396
|
+
def inspect
|
397
|
+
return "#<%p:0x%x>" % [self.class, self.object_id]
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
|
402
|
+
def convert_response(response, response_type)
|
403
|
+
case response_type
|
404
|
+
when :xml
|
405
|
+
return Nokogiri.XML(response.body)
|
406
|
+
when :raw
|
407
|
+
return response.body
|
408
|
+
when :response
|
409
|
+
return response
|
410
|
+
else
|
411
|
+
raise(GoogleDrive::Error,
|
412
|
+
"Unknown params[:response_type]: %s" % response_type)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def authenticate(mail, password, auth)
|
417
|
+
params = {
|
418
|
+
"accountType" => "HOSTED_OR_GOOGLE",
|
419
|
+
"Email" => mail,
|
420
|
+
"Passwd" => password,
|
421
|
+
"service" => auth.to_s(),
|
422
|
+
"source" => "Gimite-RubyGoogleDrive-1.00",
|
423
|
+
}
|
424
|
+
header = {"Content-Type" => "application/x-www-form-urlencoded"}
|
425
|
+
response = request(:post,
|
426
|
+
"https://www.google.com/accounts/ClientLogin",
|
427
|
+
:data => encode_query(params),
|
428
|
+
:auth => :none,
|
429
|
+
:header => header,
|
430
|
+
:response_type => :raw)
|
431
|
+
return response.slice(/^Auth=(.*)$/, 1)
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|