parallel588_google_drive 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,251 @@
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 "google_drive/util"
8
+ require "google_drive/acl"
9
+
10
+
11
+ module GoogleDrive
12
+
13
+ # A file in Google Drive, including Google Docs document/spreadsheet/presentation.
14
+ #
15
+ # Use GoogleDrive::Session#files or GoogleDrive::Session#file_by_title to
16
+ # get this object.
17
+ class File
18
+
19
+ include(Util)
20
+
21
+ def initialize(session, entry_or_url) #:nodoc:
22
+ @session = session
23
+ if !entry_or_url
24
+ # TODO Delete this after editing spreadsheet.rb.
25
+ @document_feed_entry = nil
26
+ @document_feed_url = entry_or_url
27
+ elsif entry_or_url.is_a?(String)
28
+ @document_feed_entry = nil
29
+ @document_feed_url = entry_or_url
30
+ else
31
+ @document_feed_entry = entry_or_url
32
+ # This is usually equal to the URL in <link rel="self">. But the URL in
33
+ # <link rel="self"> in collection feed is e.g.
34
+ # https://docs.google.com/feeds/default/private/full/folder%3Aroot/contents/folder%3Axxx
35
+ # and deletion of the URL doesn't delete the file itself.
36
+ # So we construct the URL here using resource ID instead.
37
+ @document_feed_url = "%s/%s?v=3" % [DOCS_BASE_URL, CGI.escape(self.resource_id)]
38
+ end
39
+ @acl = nil
40
+ end
41
+
42
+ # URL of feed used in document list feed API.
43
+ attr_reader(:document_feed_url)
44
+
45
+ # <entry> element of document list feed as Nokogiri::XML::Element.
46
+ #
47
+ # Set <tt>params[:reload]</tt> to true to force reloading the feed.
48
+ def document_feed_entry(params = {})
49
+ if !@document_feed_entry || params[:reload]
50
+ @document_feed_entry =
51
+ @session.request(:get, self.document_feed_url, :auth => :writely).css("entry")[0]
52
+ end
53
+ return @document_feed_entry
54
+ end
55
+
56
+ # Resource ID.
57
+ def resource_id
58
+ return self.document_feed_entry.css("gd|resourceId").text
59
+ end
60
+
61
+ # The type of resourse. e.g. "document", "spreadsheet", "folder"
62
+ def resource_type
63
+ return self.resource_id.split(/:/)[0]
64
+ end
65
+
66
+ # Title of the file.
67
+ #
68
+ # Set <tt>params[:reload]</tt> to true to force reloading the title.
69
+ def title(params = {})
70
+ return document_feed_entry(params).css("title").text
71
+ end
72
+
73
+ # URL to view/edit the file in a Web browser.
74
+ #
75
+ # e.g. "https://docs.google.com/file/d/xxxx/edit"
76
+ def human_url
77
+ return self.document_feed_entry.css("link[rel='alternate']")[0]["href"]
78
+ end
79
+
80
+ # ACL feed URL of the file.
81
+ def acl_feed_url
82
+ orig_acl_feed_url = self.document_feed_entry.css(
83
+ "gd|feedLink[rel='http://schemas.google.com/acl/2007#accessControlList']")[0]["href"]
84
+ case orig_acl_feed_url
85
+ when %r{^https?://docs.google.com/feeds/default/private/full/.*/acl(\?.*)?$}
86
+ return orig_acl_feed_url
87
+ when %r{^https?://docs.google.com/feeds/acl/private/full/([^\?]*)(\?.*)?$}
88
+ # URL of old API version. Converts to v3 URL.
89
+ return "#{DOCS_BASE_URL}/#{$1}/acl"
90
+ else
91
+ raise(GoogleDrive::Error,
92
+ "ACL feed URL is in unknown format: #{orig_acl_feed_url}")
93
+ end
94
+ end
95
+
96
+ # Content types you can specify in methods download_to_file, download_to_string,
97
+ # download_to_io .
98
+ def available_content_types
99
+ return self.document_feed_entry.css("content").map(){ |c| c["type"] }
100
+ end
101
+
102
+ # Downloads the file to a local file.
103
+ #
104
+ # e.g.
105
+ # file.download_to_file("/path/to/hoge.txt")
106
+ # file.download_to_file("/path/to/hoge", :content_type => "text/plain")
107
+ def download_to_file(path, params = {})
108
+ params = params.dup()
109
+ if !params[:content_type]
110
+ params[:content_type] = EXT_TO_CONTENT_TYPE[::File.extname(path).downcase]
111
+ params[:content_type_is_hint] = true
112
+ end
113
+ open(path, "wb") do |f|
114
+ download_to_io(f, params)
115
+ end
116
+ end
117
+
118
+ # Downloads the file and returns as a String.
119
+ #
120
+ # e.g.
121
+ # file.download_to_string() #=> "Hello world."
122
+ # file.download_to_string(:content_type => "text/plain") #=> "Hello world."
123
+ def download_to_string(params = {})
124
+ sio = StringIO.new()
125
+ download_to_io(sio, params)
126
+ return sio.string
127
+ end
128
+
129
+ # Downloads the file and writes it to +io+.
130
+ def download_to_io(io, params = {})
131
+ all_contents = self.document_feed_entry.css("content")
132
+ if params[:content_type] && (!params[:content_type_is_hint] || all_contents.size > 1)
133
+ contents = all_contents.select(){ |c| c["type"] == params[:content_type] }
134
+ else
135
+ contents = all_contents
136
+ end
137
+ if contents.size == 1
138
+ url = contents[0]["src"]
139
+ else
140
+ if contents.empty?
141
+ raise(GoogleDrive::Error,
142
+ ("Downloading with content type %p not supported for this file. " +
143
+ "Specify one of these to content_type: %p") %
144
+ [params[:content_type], self.available_content_types])
145
+ else
146
+ raise(GoogleDrive::Error,
147
+ ("Multiple content types are available for this file. " +
148
+ "Specify one of these to content_type: %p") %
149
+ [self.available_content_types])
150
+ end
151
+ end
152
+ # TODO Use streaming if possible.
153
+ body = @session.request(:get, url, :response_type => :raw, :auth => :writely)
154
+ io.write(body)
155
+ end
156
+
157
+ # Updates the file with the content of the local file.
158
+ #
159
+ # e.g.
160
+ # file.update_from_file("/path/to/hoge.txt")
161
+ def update_from_file(path, params = {})
162
+ params = {:file_name => ::File.basename(path)}.merge(params)
163
+ open(path, "rb") do |f|
164
+ update_from_io(f, params)
165
+ end
166
+ end
167
+
168
+ # Updates the file with +content+.
169
+ #
170
+ # e.g.
171
+ # file.update_from_string("Good bye, world.")
172
+ def update_from_string(content, params = {})
173
+ update_from_io(StringIO.new(content), params)
174
+ end
175
+
176
+ # Reads content from +io+ and updates the file with the content.
177
+ def update_from_io(io, params = {})
178
+ params = {:header => {"If-Match" => "*"}}.merge(params)
179
+ initial_url = self.document_feed_entry.css(
180
+ "link[rel='http://schemas.google.com/g/2005#resumable-edit-media']")[0]["href"]
181
+ @document_feed_entry = @session.upload_raw(
182
+ :put, initial_url, io, self.title, params)
183
+ end
184
+
185
+ # If +permanent+ is +false+, moves the file to the trash.
186
+ # If +permanent+ is +true+, deletes the file permanently.
187
+ def delete(permanent = false)
188
+ url = to_v3_url(self.document_feed_url)
189
+ url = concat_url(url, "?delete=true") if permanent
190
+ @session.request(:delete, url,
191
+ :auth => :writely, :header => {"If-Match" => "*"})
192
+ end
193
+
194
+ # Renames title of the file.
195
+ def rename(title)
196
+ edit_url = self.document_feed_entry.css("link[rel='edit']").first["href"]
197
+ xml = <<-"EOS"
198
+ <atom:entry
199
+ xmlns:atom="http://www.w3.org/2005/Atom"
200
+ xmlns:docs="http://schemas.google.com/docs/2007">
201
+ <atom:title>#{h(title)}</atom:title>
202
+ </atom:entry>
203
+ EOS
204
+ header = {"Content-Type" => "application/atom+xml", "If-Match" => "*"}
205
+ @session.request(:put, edit_url, :data => xml, :auth => :writely, :header => header)
206
+ end
207
+
208
+ alias title= rename
209
+
210
+ # Returns GoogleDrive::Acl object for the file.
211
+ #
212
+ # With the object, you can see and modify people who can access the file.
213
+ # Modifications take effect immediately.
214
+ #
215
+ # Set <tt>params[:reload]</tt> to true to force reloading the data.
216
+ #
217
+ # e.g.
218
+ # # Dumps people who have access:
219
+ # for entry in file.acl
220
+ # p [entry.scope_type, entry.scope, entry.role]
221
+ # # => e.g. ["user", "example1@gmail.com", "owner"]
222
+ # end
223
+ #
224
+ # # Shares the file with new people:
225
+ # # NOTE: This sends email to the new people.
226
+ # file.acl.push(
227
+ # {:scope_type => "user", :scope => "example2@gmail.com", :role => "reader"})
228
+ # file.acl.push(
229
+ # {:scope_type => "user", :scope => "example3@gmail.com", :role => "writer"})
230
+ #
231
+ # # Changes the role of a person:
232
+ # file.acl[1].role = "writer"
233
+ #
234
+ # # Deletes an ACL entry:
235
+ # file.acl.delete(file.acl[1])
236
+ def acl(params = {})
237
+ if !@acl || params[:reload]
238
+ @acl = Acl.new(@session, self.acl_feed_url)
239
+ end
240
+ return @acl
241
+ end
242
+
243
+ def inspect
244
+ fields = {:document_feed_url => self.document_feed_url}
245
+ fields[:title] = self.title if @document_feed_entry
246
+ return "\#<%p %s>" % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
247
+ end
248
+
249
+ end
250
+
251
+ end
@@ -0,0 +1,119 @@
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 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(GoogleDrive::Error, "Column doesn't exist: %p" % key) if !col
114
+ return col
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,88 @@
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 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
@@ -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, {:headers => extra_header})
39
+ else
40
+ raw_res = @oauth2_token.request(method, url, {:headers => 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