google_drive 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "google_drive/error"
5
+
6
+
7
+ module GoogleDrive
8
+
9
+ # Raised when GoogleDrive.login has failed.
10
+ class AuthenticationError < GoogleDrive::Error
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,56 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "net/https"
5
+ require "uri"
6
+ Net::HTTP.version_1_2
7
+
8
+
9
+ module GoogleDrive
10
+
11
+ class ClientLoginFetcher #:nodoc:
12
+
13
+ def initialize(auth_tokens, proxy)
14
+ @auth_tokens = auth_tokens
15
+ if proxy
16
+ @proxy = proxy
17
+ elsif ENV["http_proxy"] && !ENV["http_proxy"].empty?
18
+ proxy_url = URI.parse(ENV["http_proxy"])
19
+ @proxy = Net::HTTP.Proxy(proxy_url.host, proxy_url.port)
20
+ else
21
+ @proxy = Net::HTTP
22
+ end
23
+ end
24
+
25
+ attr_accessor(:auth_tokens)
26
+
27
+ def request_raw(method, url, data, extra_header, auth)
28
+ uri = URI.parse(url)
29
+ http = @proxy.new(uri.host, uri.port)
30
+ http.use_ssl = true
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+ http.start() do
33
+ path = uri.path + (uri.query ? "?#{uri.query}" : "")
34
+ header = auth_header(auth).merge(extra_header)
35
+ if method == :delete || method == :get
36
+ return http.__send__(method, path, header)
37
+ else
38
+ return http.__send__(method, path, data, header)
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def auth_header(auth)
46
+ token = auth == :none ? nil : @auth_tokens[auth]
47
+ if token
48
+ return {"Authorization" => "GoogleLogin auth=#{token}"}
49
+ else
50
+ return {}
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,54 @@
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
+ require "google_drive/spreadsheet"
7
+
8
+
9
+ module GoogleDrive
10
+
11
+ # Use GoogleDrive::Session#collection_by_url to get GoogleDrive::Collection object.
12
+ class Collection
13
+
14
+ include(Util)
15
+
16
+ def initialize(session, collection_feed_url) #:nodoc:
17
+ @session = session
18
+ @collection_feed_url = collection_feed_url
19
+ end
20
+
21
+ attr_reader(:collection_feed_url)
22
+
23
+ # Adds the given GoogleDrive::File to the collection.
24
+ def add(file)
25
+ contents_url = concat_url(@collection_feed_url, "/contents")
26
+ header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
27
+ xml = <<-"EOS"
28
+ <entry xmlns="http://www.w3.org/2005/Atom">
29
+ <id>#{h(file.document_feed_url)}</id>
30
+ </entry>
31
+ EOS
32
+ @session.request(
33
+ :post, contents_url, :data => xml, :header => header, :auth => :writely)
34
+ return nil
35
+ end
36
+
37
+ # Returns all the files in the collection.
38
+ def files
39
+ contents_url = concat_url(@collection_feed_url, "/contents")
40
+ header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
41
+ doc = @session.request(:get, contents_url, :header => header, :auth => :writely)
42
+ return doc.css("feed > entry").map(){ |e| @session.entry_element_to_file(e) }
43
+ end
44
+
45
+ # Returns all the spreadsheets in the collection.
46
+ def spreadsheets
47
+ return self.files.select(){ |f| f.is_a?(Spreadsheet) }
48
+ end
49
+
50
+ # TODO Add other operations.
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,12 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+
5
+ module GoogleDrive
6
+
7
+ # Raised when spreadsheets.google.com has returned error.
8
+ class Error < RuntimeError
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,217 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "stringio"
5
+
6
+ require "google_drive/util"
7
+ require "google_drive/acl"
8
+
9
+
10
+ module GoogleDrive
11
+
12
+ # A file in Google Drive, including Google Docs document/spreadsheet/presentation.
13
+ #
14
+ # Use GoogleDrive::Session#files or GoogleDrive::Session#file_by_title to
15
+ # get this object.
16
+ class File
17
+
18
+ include(Util)
19
+
20
+ def initialize(session, entry) #:nodoc:
21
+ @session = session
22
+ @document_feed_entry = entry
23
+ @document_feed_url = entry ? entry.css("link[rel='self']")[0]["href"] : nil
24
+ @title = entry ? entry.css("title")[0].text : nil
25
+ @acl = nil
26
+ end
27
+
28
+ # URL of feed used in document list feed API.
29
+ attr_reader(:document_feed_url)
30
+
31
+ # <entry> element of document list feed as Nokogiri::XML::Element.
32
+ attr_reader(:document_feed_entry)
33
+
34
+ # Title of the file.
35
+ attr_reader(:title)
36
+
37
+ # URL to view/edit the file in a Web browser.
38
+ #
39
+ # e.g. "https://docs.google.com/file/d/xxxx/edit"
40
+ def human_url
41
+ return self.document_feed_entry.css("link[rel='alternate']")[0]["href"]
42
+ end
43
+
44
+ # ACL feed URL of the file.
45
+ def acl_feed_url
46
+ orig_acl_feed_url = self.document_feed_entry.css(
47
+ "gd|feedLink[rel='http://schemas.google.com/acl/2007#accessControlList']")[0]["href"]
48
+ case orig_acl_feed_url
49
+ when %r{^https?://docs.google.com/feeds/default/private/full/.*/acl(\?.*)?$}
50
+ return orig_acl_feed_url
51
+ when %r{^https?://docs.google.com/feeds/acl/private/full/([^\?]*)(\?.*)?$}
52
+ # URL of old API version. Converts to v3 URL.
53
+ return "https://docs.google.com/feeds/default/private/full/#{$1}/acl"
54
+ else
55
+ raise(GoogleDrive::Error,
56
+ "ACL feed URL is in unknown format: #{orig_acl_feed_url}")
57
+ end
58
+ end
59
+
60
+ # Content types you can specify in methods download_to_file, download_to_string,
61
+ # download_to_io .
62
+ def available_content_types
63
+ return self.document_feed_entry.css("content").map(){ |c| c["type"] }
64
+ end
65
+
66
+ # Downloads the file to a local file.
67
+ #
68
+ # e.g.
69
+ # file.download_to_file("/path/to/hoge.txt")
70
+ # file.download_to_file("/path/to/hoge", :content_type => "text/plain")
71
+ def download_to_file(path, params = {})
72
+ params = params.dup()
73
+ if !params[:content_type]
74
+ params[:content_type] = EXT_TO_CONTENT_TYPE[::File.extname(path).downcase]
75
+ params[:content_type_is_hint] = true
76
+ end
77
+ open(path, "wb") do |f|
78
+ download_to_io(f, params)
79
+ end
80
+ end
81
+
82
+ # Downloads the file and returns as a String.
83
+ #
84
+ # e.g.
85
+ # file.download_to_string() #=> "Hello world."
86
+ # file.download_to_string(:content_type => "text/plain") #=> "Hello world."
87
+ def download_to_string(params = {})
88
+ sio = StringIO.new()
89
+ download_to_io(sio, params)
90
+ return sio.string
91
+ end
92
+
93
+ # Downloads the file and writes it to +io+.
94
+ def download_to_io(io, params = {})
95
+ all_contents = self.document_feed_entry.css("content")
96
+ if params[:content_type] && (!params[:content_type_is_hint] || all_contents.size > 1)
97
+ contents = all_contents.select(){ |c| c["type"] == params[:content_type] }
98
+ else
99
+ contents = all_contents
100
+ end
101
+ if contents.size == 1
102
+ url = contents[0]["src"]
103
+ else
104
+ if contents.empty?
105
+ raise(GoogleDrive::Error,
106
+ ("Downloading with content type %p not supported for this file. " +
107
+ "Specify one of these to content_type: %p") %
108
+ [params[:content_type], self.available_content_types])
109
+ else
110
+ raise(GoogleDrive::Error,
111
+ ("Multiple content types are available for this file. " +
112
+ "Specify one of these to content_type: %p") %
113
+ [self.available_content_types])
114
+ end
115
+ end
116
+ # TODO Use streaming if possible.
117
+ body = @session.request(:get, url, :response_type => :raw, :auth => :writely)
118
+ io.write(body)
119
+ end
120
+
121
+ # Updates the file with the content of the local file.
122
+ #
123
+ # e.g.
124
+ # file.update_from_file("/path/to/hoge.txt")
125
+ def update_from_file(path, params = {})
126
+ params = {:file_name => ::File.basename(path)}.merge(params)
127
+ open(path, "rb") do |f|
128
+ update_from_io(f, params)
129
+ end
130
+ end
131
+
132
+ # Updates the file with +content+.
133
+ #
134
+ # e.g.
135
+ # file.update_from_string("Good bye, world.")
136
+ def update_from_string(content, params = {})
137
+ update_from_io(StringIO.new(content), params)
138
+ end
139
+
140
+ # Reads content from +io+ and updates the file with the content.
141
+ def update_from_io(io, params = {})
142
+ params = {:header => {"If-Match" => "*"}}.merge(params)
143
+ initial_url = self.document_feed_entry.css(
144
+ "link[rel='http://schemas.google.com/g/2005#resumable-edit-media']")[0]["href"]
145
+ @document_feed_entry = @session.upload_raw(
146
+ :put, initial_url, io, self.title, params)
147
+ end
148
+
149
+ # If +permanent+ is +false+, moves the file to the trash.
150
+ # If +permanent+ is +true+, deletes the file permanently.
151
+ def delete(permanent = false)
152
+ @session.request(:delete,
153
+ self.document_feed_url + (permanent ? "?delete=true" : ""),
154
+ :auth => :writely, :header => {"If-Match" => "*"})
155
+ end
156
+
157
+ # Renames title of the file.
158
+ def rename(title)
159
+
160
+ doc = @session.request(:get, self.document_feed_url, :auth => :writely)
161
+ edit_url = doc.css("link[rel='edit']").first["href"]
162
+ xml = <<-"EOS"
163
+ <atom:entry
164
+ xmlns:atom="http://www.w3.org/2005/Atom"
165
+ xmlns:docs="http://schemas.google.com/docs/2007">
166
+ <atom:title>#{h(title)}</atom:title>
167
+ </atom:entry>
168
+ EOS
169
+
170
+ @session.request(
171
+ :put, edit_url, :data => xml, :auth => :writely,
172
+ :header => {"Content-Type" => "application/atom+xml", "If-Match" => "*"})
173
+
174
+ end
175
+
176
+ alias title= rename
177
+
178
+ # Returns GoogleDrive::Acl object for the file.
179
+ #
180
+ # With the object, you can see and modify people who can access the file.
181
+ # Modifications take effect immediately.
182
+ #
183
+ # Set <tt>params[:reload]</tt> to true to force reloading the title.
184
+ #
185
+ # e.g.
186
+ # # Dumps people who have access:
187
+ # for entry in file.acl
188
+ # p [entry.scope_type, entry.scope, entry.role]
189
+ # # => e.g. ["user", "example1@gmail.com", "owner"]
190
+ # end
191
+ #
192
+ # # Shares the file with new people:
193
+ # # NOTE: This sends email to the new people.
194
+ # file.acl.push(
195
+ # {:scope_type => "user", :scope => "example2@gmail.com", :role => "reader"})
196
+ # file.acl.push(
197
+ # {:scope_type => "user", :scope => "example3@gmail.com", :role => "writer"})
198
+ #
199
+ # # Changes the role of a person:
200
+ # file.acl[1].role = "writer"
201
+ #
202
+ # # Deletes an ACL entry:
203
+ # file.acl.delete(file.acl[1])
204
+ def acl(params = {})
205
+ if !@acl || params[:reload]
206
+ @acl = Acl.new(@session, self.acl_feed_url)
207
+ end
208
+ return @acl
209
+ end
210
+
211
+ def inspect
212
+ return "\#<%p document_feed_url=%p>" % [self.class, self.document_feed_url]
213
+ end
214
+
215
+ end
216
+
217
+ end
@@ -0,0 +1,115 @@
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
+ require "google_drive/list_row"
7
+
8
+
9
+ module GoogleDrive
10
+
11
+ # Provides access to cells using column names.
12
+ # Use GoogleDrive::Worksheet#list to get GoogleDrive::List object.
13
+ #--
14
+ # This is implemented as wrapper of GoogleDrive::Worksheet i.e. using cells
15
+ # feed, not list feed. In this way, we can easily provide consistent API as
16
+ # GoogleDrive::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 (GoogleDrive::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 GoogleDrive::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 GoogleDrive::Worksheet#save().
46
+ def []=(index, hash)
47
+ self[index].replace(hash)
48
+ end
49
+
50
+ # Iterates over Hash-like object (GoogleDrive::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 GoogleDrive::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 GoogleDrive::ListRow for the new row.
81
+ #
82
+ # Note that update is not sent to the server until
83
+ # you call GoogleDrive::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 set(index, key, value) #:nodoc:
101
+ @worksheet[index + 2, key_to_col(key)] = value
102
+ end
103
+
104
+ private
105
+
106
+ def key_to_col(key)
107
+ key = key.to_s()
108
+ col = (1..@worksheet.num_cols).find(){ |c| @worksheet[1, c] == key }
109
+ raise(GoogleDrive::Error, "Colunm doesn't exist: %p" % key) if !col
110
+ return col
111
+ end
112
+
113
+ end
114
+
115
+ end