google_drive 0.3.11 → 1.0.0.pre1

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.
Files changed (34) hide show
  1. checksums.yaml +7 -7
  2. data/README.rdoc +27 -10
  3. data/lib/google_drive/acl.rb +40 -58
  4. data/lib/google_drive/acl_entry.rb +76 -56
  5. data/lib/google_drive/api_client_fetcher.rb +49 -0
  6. data/lib/google_drive/collection.rb +69 -71
  7. data/lib/google_drive/file.rb +171 -128
  8. data/lib/google_drive/session.rb +234 -268
  9. data/lib/google_drive/spreadsheet.rb +19 -163
  10. data/lib/google_drive/util.rb +126 -17
  11. data/lib/google_drive/worksheet.rb +108 -80
  12. data/lib/google_drive.rb +63 -57
  13. data/lib/google_drive_v1/acl.rb +115 -0
  14. data/lib/google_drive_v1/acl_entry.rb +100 -0
  15. data/lib/google_drive_v1/api_client_fetcher.rb +47 -0
  16. data/lib/google_drive_v1/authentication_error.rb +14 -0
  17. data/lib/{google_drive → google_drive_v1}/basic_fetcher.rb +1 -1
  18. data/lib/{google_drive → google_drive_v1}/client_login_fetcher.rb +2 -2
  19. data/lib/google_drive_v1/collection.rb +167 -0
  20. data/lib/google_drive_v1/error.rb +12 -0
  21. data/lib/google_drive_v1/file.rb +258 -0
  22. data/lib/google_drive_v1/list.rb +119 -0
  23. data/lib/google_drive_v1/list_row.rb +88 -0
  24. data/lib/{google_drive → google_drive_v1}/oauth1_fetcher.rb +1 -1
  25. data/lib/{google_drive → google_drive_v1}/oauth2_fetcher.rb +2 -2
  26. data/lib/google_drive_v1/record.rb +31 -0
  27. data/lib/google_drive_v1/session.rb +522 -0
  28. data/lib/google_drive_v1/spreadsheet.rb +248 -0
  29. data/lib/google_drive_v1/table.rb +60 -0
  30. data/lib/google_drive_v1/util.rb +73 -0
  31. data/lib/google_drive_v1/worksheet.rb +498 -0
  32. data/lib/google_drive_v1.rb +148 -0
  33. metadata +112 -77
  34. data/doc_src/google_drive/acl_entry.rb +0 -33
@@ -2,6 +2,7 @@
2
2
  # The license of this source is "New BSD Licence"
3
3
 
4
4
  require "cgi"
5
+ require "forwardable"
5
6
  require "stringio"
6
7
 
7
8
  require "google_drive/util"
@@ -14,109 +15,90 @@ module GoogleDrive
14
15
  #
15
16
  # Use GoogleDrive::Session#files or GoogleDrive::Session#file_by_title to
16
17
  # get this object.
18
+ #
19
+ # In addition to the methods below, properties defined here are also available as attributes:
20
+ # https://developers.google.com/drive/v2/reference/files#resource
21
+ #
22
+ # e.g.,
23
+ # file.mime_type # ==> "text/plain"
17
24
  class File
18
25
 
19
26
  include(Util)
27
+ extend(Forwardable)
20
28
 
21
- def initialize(session, entry_or_url) #:nodoc:
29
+ def initialize(session, api_file) #:nodoc:
22
30
  @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
31
+ @api_file = api_file
39
32
  @acl = nil
33
+ delegate_api_methods(self, @api_file, ["title"])
40
34
  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
- warn(
50
- "WARNING: GoogleDrive::file\#document_feed_entry is deprecated and will be removed " +
51
- "in the next version.")
52
- return self.document_feed_entry_internal(params)
53
- end
54
35
 
55
- def document_feed_entry_internal(params = {}) #:nodoc:
56
- if !@document_feed_entry || params[:reload]
57
- @document_feed_entry =
58
- @session.request(:get, self.document_feed_url, :auth => :writely).css("entry")[0]
36
+ # Wrapped Google::APIClient::Schema::Drive::V2::File object.
37
+ attr_reader(:api_file)
38
+
39
+ # Reloads file metadata such as title and acl.
40
+ def reload_metadata()
41
+ api_result = @session.execute!(
42
+ :api_method => @session.drive.files.get,
43
+ :parameters => { "fileId" => self.id })
44
+ @api_file = api_result.data
45
+ if @acl
46
+ @acl = Acl.new(@session, self)
59
47
  end
60
- return @document_feed_entry
61
48
  end
62
49
 
63
- # Resource ID.
50
+ # Returns resource_type + ":" + id.
64
51
  def resource_id
65
- return self.document_feed_entry_internal.css("gd|resourceId").text
52
+ return "%s:%s" % [self.resource_type, self.id]
53
+ end
54
+
55
+ # URL of feed used in the deprecated document list feed API.
56
+ def document_feed_url
57
+ return "https://docs.google.com/feeds/default/private/full/" + CGI.escape(self.resource_id)
58
+ end
59
+
60
+ # Deprecated ACL feed URL of the file.
61
+ def acl_feed_url
62
+ return self.document_feed_url + "/acl"
66
63
  end
67
64
 
68
65
  # The type of resourse. e.g. "document", "spreadsheet", "folder"
69
66
  def resource_type
70
- return self.resource_id.split(/:/)[0]
67
+ return self.mime_type.slice(/^application\/vnd.google-apps.(.+)$/, 1) || "file"
71
68
  end
72
69
 
73
70
  # Title of the file.
74
- #
75
- # Set <tt>params[:reload]</tt> to true to force reloading the title.
76
71
  def title(params = {})
77
- return document_feed_entry_internal(params).css("title").text
72
+ reload_metadata() if params[:reload]
73
+ return self.api_file.title
78
74
  end
79
75
 
80
76
  # URL to view/edit the file in a Web browser.
81
77
  #
82
78
  # e.g. "https://docs.google.com/file/d/xxxx/edit"
83
79
  def human_url
84
- return self.document_feed_entry_internal.css("link[rel='alternate']")[0]["href"]
80
+ return self.alternate_link
85
81
  end
86
82
 
87
- # ACL feed URL of the file.
88
- def acl_feed_url
89
- orig_acl_feed_url = self.document_feed_entry_internal.css(
90
- "gd|feedLink[rel='http://schemas.google.com/acl/2007#accessControlList']")[0]["href"]
91
- case orig_acl_feed_url
92
- when %r{^https?://docs.google.com/feeds/default/private/full/.*/acl(\?.*)?$}
93
- return orig_acl_feed_url
94
- when %r{^https?://docs.google.com/feeds/acl/private/full/([^\?]*)(\?.*)?$}
95
- # URL of old API version. Converts to v3 URL.
96
- return "#{DOCS_BASE_URL}/#{$1}/acl"
97
- else
98
- raise(GoogleDrive::Error,
99
- "ACL feed URL is in unknown format: #{orig_acl_feed_url}")
100
- end
101
- end
102
-
103
83
  # Content types you can specify in methods download_to_file, download_to_string,
104
84
  # download_to_io .
85
+ #
86
+ # This returns zero or one file type. You may be able to download the file in other formats using
87
+ # export_as_file, export_as_string, or export_to_io. Use export_links method to get available formats
88
+ # in these methods.
105
89
  def available_content_types
106
- return self.document_feed_entry_internal.css("content").map(){ |c| c["type"] }
90
+ if self.api_file.download_url
91
+ return [self.api_file.mime_type]
92
+ else
93
+ return []
94
+ end
107
95
  end
108
96
 
109
- # Downloads the file to a local file.
110
- #
111
- # e.g.
97
+ # Downloads the file to a local file. e.g.
112
98
  # file.download_to_file("/path/to/hoge.txt")
113
- # file.download_to_file("/path/to/hoge", :content_type => "text/plain")
99
+ #
100
+ # To export the file in other formats, use export_as_file.
114
101
  def download_to_file(path, params = {})
115
- params = params.dup()
116
- if !params[:content_type]
117
- params[:content_type] = EXT_TO_CONTENT_TYPE[::File.extname(path).downcase]
118
- params[:content_type_is_hint] = true
119
- end
120
102
  open(path, "wb") do |f|
121
103
  download_to_io(f, params)
122
104
  end
@@ -124,9 +106,7 @@ module GoogleDrive
124
106
 
125
107
  # Downloads the file and returns as a String.
126
108
  #
127
- # e.g.
128
- # file.download_to_string() #=> "Hello world."
129
- # file.download_to_string(:content_type => "text/plain") #=> "Hello world."
109
+ # To export the file in other formats, use export_as_string.
130
110
  def download_to_string(params = {})
131
111
  sio = StringIO.new()
132
112
  download_to_io(sio, params)
@@ -134,42 +114,73 @@ module GoogleDrive
134
114
  end
135
115
 
136
116
  # Downloads the file and writes it to +io+.
117
+ #
118
+ # To export the file in other formats, use export_to_io.
137
119
  def download_to_io(io, params = {})
138
- all_contents = self.document_feed_entry_internal.css("content")
139
- if params[:content_type] && (!params[:content_type_is_hint] || all_contents.size > 1)
140
- contents = all_contents.select(){ |c| c["type"] == params[:content_type] }
141
- else
142
- contents = all_contents
120
+ if !self.api_file.download_url
121
+ raise(GoogleDrive::Error, "Downloading is not supported for this file.")
143
122
  end
144
- if contents.size == 1
145
- url = contents[0]["src"]
146
- else
147
- if contents.empty?
148
- raise(GoogleDrive::Error,
149
- ("Downloading with content type %p not supported for this file. " +
150
- "Specify one of these to content_type: %p") %
151
- [params[:content_type], self.available_content_types])
152
- else
153
- raise(GoogleDrive::Error,
154
- ("Multiple content types are available for this file. " +
155
- "Specify one of these to content_type: %p") %
156
- [self.available_content_types])
123
+ # TODO Use streaming if possible.
124
+ api_result = @session.execute!(:uri => self.api_file.download_url)
125
+ io.write(api_result.body)
126
+ end
127
+
128
+ # Export the file to +path+ in content type +format+.
129
+ # If +format+ is nil, it is guessed from the file name.
130
+ #
131
+ # e.g.,
132
+ # spreadsheet.export_as_file("/path/to/hoge.csv")
133
+ # spreadsheet.export_as_file("/path/to/hoge", "text/csv")
134
+ #
135
+ # If you want to download the file in the original format, use download_to_file instead.
136
+ def export_as_file(path, format = nil)
137
+ if !format
138
+ format = EXT_TO_CONTENT_TYPE[::File.extname(path).downcase]
139
+ if !format
140
+ raise(ArgumentError,
141
+ ("Cannot guess format from the file name: %s\n" +
142
+ "Specify format argument explicitly.") %
143
+ path)
157
144
  end
158
145
  end
159
- # TODO Use streaming if possible.
160
- body = @session.request(:get, url, :response_type => :raw, :auth => :writely)
161
- io.write(body)
146
+ open(path, "wb") do |f|
147
+ export_to_io(f, format)
148
+ end
162
149
  end
163
150
 
164
- # Updates the file with the content of the local file.
151
+ # Export the file as String in content type +format+.
165
152
  #
166
- # e.g.
167
- # file.update_from_file("/path/to/hoge.txt")
168
- def update_from_file(path, params = {})
169
- params = {:file_name => ::File.basename(path)}.merge(params)
170
- open(path, "rb") do |f|
171
- update_from_io(f, params)
153
+ # e.g.,
154
+ # spreadsheet.export_as_string("text/csv")
155
+ #
156
+ # If you want to download the file in the original format, use download_to_string instead.
157
+ def export_as_string(format)
158
+ sio = StringIO.new()
159
+ export_to_io(sio, format)
160
+ return sio.string
161
+ end
162
+
163
+ # Export the file to +io+ in content type +format+.
164
+ #
165
+ # If you want to download the file in the original format, use download_to_io instead.
166
+ def export_to_io(io, format)
167
+ mime_type = EXT_TO_CONTENT_TYPE["." + format] || format
168
+ if !self.export_links
169
+ raise(
170
+ GoogleDrive::Error,
171
+ "This file doesn't support exporting. You may still download the file in the " +
172
+ "original format using download_to_file, download_to_string or download_to_io.")
173
+ end
174
+ export_url = self.export_links[mime_type]
175
+ if !export_url
176
+ raise(
177
+ GoogleDrive::Error,
178
+ "This file doesn't support export with mime type %p. Supported mime types: %p" %
179
+ [mime_type, self.export_links.to_hash().keys])
172
180
  end
181
+ # TODO Use streaming if possible.
182
+ api_result = @session.execute!(:uri => export_url)
183
+ io.write(api_result.body)
173
184
  end
174
185
 
175
186
  # Updates the file with +content+.
@@ -177,50 +188,84 @@ module GoogleDrive
177
188
  # e.g.
178
189
  # file.update_from_string("Good bye, world.")
179
190
  def update_from_string(content, params = {})
180
- update_from_io(StringIO.new(content), params)
191
+ media = new_upload_io(StringIO.new(content), params)
192
+ return update_from_media(media, params)
181
193
  end
182
194
 
195
+ # Updates the file with the content of the local file.
196
+ #
197
+ # e.g.
198
+ # file.update_from_file("/path/to/hoge.txt")
199
+ def update_from_file(path, params = {})
200
+ file_name = ::File.basename(path)
201
+ params = {:file_name => file_name}.merge(params)
202
+ media = new_upload_io(path, params)
203
+ return update_from_media(media, params)
204
+ end
205
+
183
206
  # Reads content from +io+ and updates the file with the content.
184
207
  def update_from_io(io, params = {})
185
- params = {:header => {"If-Match" => "*"}}.merge(params)
186
- initial_url = self.document_feed_entry_internal.css(
187
- "link[rel='http://schemas.google.com/g/2005#resumable-edit-media']")[0]["href"]
188
- @document_feed_entry = @session.upload_raw(
189
- :put, initial_url, io, self.title, params)
208
+ media = new_upload_io(io, params)
209
+ return update_from_media(media, params)
190
210
  end
191
-
211
+
212
+ # Reads content from +media+ and updates the file with the content.
213
+ def update_from_media(media, params = {})
214
+ api_result = @session.execute!(
215
+ :api_method => @session.drive.files.update,
216
+ :media => media,
217
+ :parameters => {
218
+ "fileId" => self.id,
219
+ "uploadType" => "media",
220
+ })
221
+ return @session.wrap_api_file(api_result.data)
222
+ end
223
+
192
224
  # If +permanent+ is +false+, moves the file to the trash.
193
225
  # If +permanent+ is +true+, deletes the file permanently.
194
226
  def delete(permanent = false)
195
- url = to_v3_url(self.document_feed_url)
196
- url = concat_url(url, "?delete=true") if permanent
197
- @session.request(:delete, url,
198
- :auth => :writely, :header => {"If-Match" => "*"})
227
+ if permanent
228
+ @session.execute!(
229
+ :api_method => @session.drive.files.delete,
230
+ :parameters => {"fileId" => self.id})
231
+ else
232
+ @session.execute!(
233
+ :api_method => @session.drive.files.trash,
234
+ :parameters => {"fileId" => self.id})
235
+ end
236
+ return nil
199
237
  end
200
238
 
201
239
  # Renames title of the file.
202
240
  def rename(title)
203
- edit_url = self.document_feed_entry_internal.css("link[rel='edit']").first["href"]
204
- xml = <<-"EOS"
205
- <atom:entry
206
- xmlns:atom="http://www.w3.org/2005/Atom"
207
- xmlns:docs="http://schemas.google.com/docs/2007">
208
- <atom:title>#{h(title)}</atom:title>
209
- </atom:entry>
210
- EOS
211
- header = {"Content-Type" => "application/atom+xml;charset=utf-8", "If-Match" => "*"}
212
- @session.request(:put, edit_url, :data => xml, :auth => :writely, :header => header)
241
+ api_result = @session.execute!(
242
+ :api_method => @session.drive.files.patch,
243
+ :body_object => {"title" => title},
244
+ :parameters => {"fileId" => self.id})
245
+ @api_file = api_result.data
213
246
  end
214
247
 
215
248
  alias title= rename
249
+
250
+ # Creates copy of this file with the given title.
251
+ def copy(title)
252
+ copied_file = @session.drive.files.copy.request_schema.new({
253
+ "title" => title,
254
+ })
255
+ api_result = @session.execute!(
256
+ :api_method => @session.drive.files.copy,
257
+ :body_object => copied_file,
258
+ :parameters => {"fileId" => self.id})
259
+ return @session.wrap_api_file(api_result.data)
260
+ end
261
+
262
+ alias duplicate copy
216
263
 
217
264
  # Returns GoogleDrive::Acl object for the file.
218
265
  #
219
266
  # With the object, you can see and modify people who can access the file.
220
267
  # Modifications take effect immediately.
221
268
  #
222
- # Set <tt>params[:reload]</tt> to true to force reloading the data.
223
- #
224
269
  # e.g.
225
270
  # # Dumps people who have access:
226
271
  # for entry in file.acl
@@ -231,9 +276,9 @@ module GoogleDrive
231
276
  # # Shares the file with new people:
232
277
  # # NOTE: This sends email to the new people.
233
278
  # file.acl.push(
234
- # {:scope_type => "user", :scope => "example2@gmail.com", :role => "reader"})
279
+ # {:type => "user", :value => "example2@gmail.com", :role => "reader"})
235
280
  # file.acl.push(
236
- # {:scope_type => "user", :scope => "example3@gmail.com", :role => "writer"})
281
+ # {:type => "user", :value => "example3@gmail.com", :role => "writer"})
237
282
  #
238
283
  # # Changes the role of a person:
239
284
  # file.acl[1].role = "writer"
@@ -242,15 +287,13 @@ module GoogleDrive
242
287
  # file.acl.delete(file.acl[1])
243
288
  def acl(params = {})
244
289
  if !@acl || params[:reload]
245
- @acl = Acl.new(@session, self.acl_feed_url)
290
+ @acl = Acl.new(@session, self)
246
291
  end
247
292
  return @acl
248
293
  end
249
294
 
250
295
  def inspect
251
- fields = {:document_feed_url => self.document_feed_url}
252
- fields[:title] = self.title if @document_feed_entry
253
- return "\#<%p %s>" % [self.class, fields.map(){ |k, v| "%s=%p" % [k, v] }.join(", ")]
296
+ return "\#<%p id=%p title=%p>" % [self.class, self.id, self.title]
254
297
  end
255
298
 
256
299
  end