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.
@@ -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