rdropbox 1.0.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/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +84 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/dropbox.gemspec +87 -0
- data/lib/dropbox/api.rb +530 -0
- data/lib/dropbox/entry.rb +96 -0
- data/lib/dropbox/event.rb +109 -0
- data/lib/dropbox/memoization.rb +98 -0
- data/lib/dropbox/revision.rb +197 -0
- data/lib/dropbox/session.rb +160 -0
- data/lib/dropbox.rb +43 -0
- data/lib/extensions/array.rb +9 -0
- data/lib/extensions/hash.rb +61 -0
- data/lib/extensions/module.rb +22 -0
- data/lib/extensions/object.rb +5 -0
- data/lib/extensions/string.rb +9 -0
- data/lib/extensions/to_bool.rb +17 -0
- data/spec/dropbox/api_spec.rb +778 -0
- data/spec/dropbox/entry_spec.rb +144 -0
- data/spec/dropbox/event_spec.rb +122 -0
- data/spec/dropbox/revision_spec.rb +367 -0
- data/spec/dropbox/session_spec.rb +148 -0
- data/spec/dropbox_spec.rb +57 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +150 -0
data/lib/dropbox/api.rb
ADDED
@@ -0,0 +1,530 @@
|
|
1
|
+
# Defines the Dropbox::API module.
|
2
|
+
|
3
|
+
require "#{File.expand_path File.dirname(__FILE__)}/memoization"
|
4
|
+
require 'json'
|
5
|
+
require 'net/http/post/multipart'
|
6
|
+
|
7
|
+
module Dropbox
|
8
|
+
|
9
|
+
# Extensions to the Dropbox::Session class that add core Dropbox API
|
10
|
+
# functionality to this class. You must have authenticated your
|
11
|
+
# Dropbox::Session instance before you can call any of these methods. (See the
|
12
|
+
# Dropbox::Session class documentation for instructions.)
|
13
|
+
#
|
14
|
+
# API methods generally return +Struct+ objects containing their results,
|
15
|
+
# unless otherwise noted. See the Dropbox API documentation at
|
16
|
+
# http://developers.dropbox.com for specific information on the schema of each
|
17
|
+
# result.
|
18
|
+
#
|
19
|
+
# You can opt-in to memoization of API method results. See the
|
20
|
+
# Dropbox::Memoization class documentation to learn more.
|
21
|
+
#
|
22
|
+
# == Modes
|
23
|
+
#
|
24
|
+
# The Dropbox API works in three modes: sandbox, Dropbox (root), and
|
25
|
+
# metadata-only.
|
26
|
+
#
|
27
|
+
# * In sandbox mode (the default), all operations are rooted from your
|
28
|
+
# application's sandbox folder; other files elsewhere on the user's Dropbox
|
29
|
+
# are inaccessible.
|
30
|
+
# * In Dropbox mode, the root is the user's Dropbox folder, and all files are
|
31
|
+
# accessible. This mode is typically only available to certain API users.
|
32
|
+
# * In metadata-only mode, the root is the Dropbox folder, but read-only
|
33
|
+
# access is not available. Operations that modify the user's files will
|
34
|
+
# fail.
|
35
|
+
#
|
36
|
+
# You should configure the Dropbox::Session instance to use whichever mode
|
37
|
+
# you chose when you set up your application:
|
38
|
+
#
|
39
|
+
# session.mode = :metadata_only
|
40
|
+
#
|
41
|
+
# Valid values are listed in Dropbox::API::MODES, and this step is not
|
42
|
+
# necessary for sandboxed applications, as the sandbox mode is the default.
|
43
|
+
#
|
44
|
+
# You can also temporarily change the mode for many method calls using their
|
45
|
+
# options hash:
|
46
|
+
#
|
47
|
+
# session.move 'my_file', 'new/path', :mode => :dropbox
|
48
|
+
|
49
|
+
module API
|
50
|
+
include Dropbox::Memoization
|
51
|
+
|
52
|
+
# Valid API modes for the #mode= method.
|
53
|
+
MODES = [ :sandbox, :dropbox, :metadata_only ]
|
54
|
+
|
55
|
+
# Returns a Dropbox::Entry instance that can be used to work with files or
|
56
|
+
# directories in an object-oriented manner.
|
57
|
+
|
58
|
+
def entry(path)
|
59
|
+
Dropbox::Entry.new(self, path)
|
60
|
+
end
|
61
|
+
alias :file :entry
|
62
|
+
alias :directory :entry
|
63
|
+
alias :dir :entry
|
64
|
+
|
65
|
+
# Returns a +Struct+ with information about the user's account. See
|
66
|
+
# http://developers.dropbox.com/python/base.html#account-info for more
|
67
|
+
# information on the data returned.
|
68
|
+
|
69
|
+
def account
|
70
|
+
get('account', 'info', :ssl => @ssl).to_struct_recursively
|
71
|
+
end
|
72
|
+
memoize :account
|
73
|
+
|
74
|
+
# Downloads the file at the given path relative to the configured mode's
|
75
|
+
# root.
|
76
|
+
#
|
77
|
+
# Returns the contents of the downloaded file as a +String+. Support for
|
78
|
+
# streaming downloads and range queries is available server-side, but not
|
79
|
+
# available in this API client due to limitations of the OAuth gem.
|
80
|
+
#
|
81
|
+
# Options:
|
82
|
+
#
|
83
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
84
|
+
|
85
|
+
def download(path, options={})
|
86
|
+
path.sub! /^\//, ''
|
87
|
+
rest = Dropbox.check_path(path).split('/')
|
88
|
+
rest << { :ssl => @ssl }
|
89
|
+
api_body :get, 'files', root(options), *rest
|
90
|
+
#TODO streaming, range queries
|
91
|
+
end
|
92
|
+
|
93
|
+
# Uploads a file to a path relative to the configured mode's root. The
|
94
|
+
# +remote_path+ parameter is taken to be the path portion _only_; the name
|
95
|
+
# of the remote file will be identical to that of the local file. You can
|
96
|
+
# provide any of the following for the first parameter:
|
97
|
+
#
|
98
|
+
# * a +File+ object, in which case the name of the local file is used, or
|
99
|
+
# * a path to a file, in which case that file's name is used.
|
100
|
+
#
|
101
|
+
# Options:
|
102
|
+
#
|
103
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
104
|
+
#
|
105
|
+
# Examples:
|
106
|
+
#
|
107
|
+
# session.upload 'music.pdf', '/' # upload a file by path to the root directory
|
108
|
+
# session.upload 'music.pdf, 'music/' # upload a file by path to the music folder
|
109
|
+
# session.upload File.new('music.pdf'), '/' # same as the first example
|
110
|
+
|
111
|
+
def upload(local_file, remote_path, options={})
|
112
|
+
if local_file.kind_of?(File) or local_file.kind_of?(Tempfile) then
|
113
|
+
file = local_file
|
114
|
+
name = local_file.respond_to?(:original_filename) ? local_file.original_filename : File.basename(local_file.path)
|
115
|
+
local_path = local_file.path
|
116
|
+
elsif local_file.kind_of?(String) then
|
117
|
+
file = File.new(local_file)
|
118
|
+
name = File.basename(local_file)
|
119
|
+
local_path = local_file
|
120
|
+
else
|
121
|
+
raise ArgumentError, "local_file must be a File or file path"
|
122
|
+
end
|
123
|
+
|
124
|
+
remote_path.sub! /^\//, ''
|
125
|
+
remote_path = Dropbox.check_path(remote_path).split('/')
|
126
|
+
|
127
|
+
remote_path << { :ssl => @ssl }
|
128
|
+
url = Dropbox.api_url('files', root(options), *remote_path)
|
129
|
+
uri = URI.parse(url)
|
130
|
+
|
131
|
+
oauth_request = Net::HTTP::Post.new(uri.path)
|
132
|
+
oauth_request.set_form_data 'file' => name
|
133
|
+
|
134
|
+
alternate_host_session = clone_with_host(@ssl ? Dropbox::ALTERNATE_SSL_HOSTS['files'] : Dropbox::ALTERNATE_HOSTS['files'])
|
135
|
+
alternate_host_session.instance_variable_get(:@consumer).sign!(oauth_request, @access_token)
|
136
|
+
oauth_signature = oauth_request.to_hash['authorization']
|
137
|
+
|
138
|
+
request = Net::HTTP::Post::Multipart.new(uri.path,
|
139
|
+
'file' => UploadIO.convert!(
|
140
|
+
file,
|
141
|
+
'application/octet-stream',
|
142
|
+
name,
|
143
|
+
local_path))
|
144
|
+
request['authorization'] = oauth_signature.join(', ')
|
145
|
+
|
146
|
+
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(request) }
|
147
|
+
if response.kind_of?(Net::HTTPSuccess) then
|
148
|
+
begin
|
149
|
+
return JSON.parse(response.body).symbolize_keys_recursively.to_struct_recursively
|
150
|
+
rescue JSON::ParserError
|
151
|
+
raise ParseError.new(uri.to_s, response)
|
152
|
+
end
|
153
|
+
else
|
154
|
+
raise UnsuccessfulResponseError.new(uri.to_s, response)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Copies the +source+ file to the path at +target+. If +target+ ends with a
|
159
|
+
# slash, the new file will share the same name as the old file. Returns a
|
160
|
+
# +Struct+ with metadata for the new file. (See the metadata method.)
|
161
|
+
#
|
162
|
+
# Both paths are assumed to be relative to the configured mode's root.
|
163
|
+
#
|
164
|
+
# Raises FileNotFoundError if +source+ does not exist. Raises
|
165
|
+
# FileExistsError if +target+ already exists.
|
166
|
+
#
|
167
|
+
# Options:
|
168
|
+
#
|
169
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
170
|
+
#
|
171
|
+
# TODO The API documentation says this method returns 404/403 if the source or target is invalid, but it actually returns 5xx.
|
172
|
+
|
173
|
+
def copy(source, target, options={})
|
174
|
+
source.sub! /^\//, ''
|
175
|
+
target.sub! /^\//, ''
|
176
|
+
target << File.basename(source) if target.ends_with?('/')
|
177
|
+
begin
|
178
|
+
parse_metadata(post('fileops', 'copy', :from_path => Dropbox.check_path(source), :to_path => Dropbox.check_path(target), :root => root(options), :ssl => @ssl)).to_struct_recursively
|
179
|
+
rescue UnsuccessfulResponseError => error
|
180
|
+
raise FileNotFoundError.new(source) if error.response.kind_of?(Net::HTTPNotFound)
|
181
|
+
raise FileExistsError.new(target) if error.response.kind_of?(Net::HTTPForbidden)
|
182
|
+
raise error
|
183
|
+
end
|
184
|
+
end
|
185
|
+
alias :cp :copy
|
186
|
+
|
187
|
+
# Creates a folder at the given path. The path is assumed to be relative to
|
188
|
+
# the configured mode's root. Returns a +Struct+ with metadata about the new
|
189
|
+
# folder. (See the metadata method.)
|
190
|
+
#
|
191
|
+
# Raises FileExistsError if there is already a file or folder at +path+.
|
192
|
+
#
|
193
|
+
# Options:
|
194
|
+
#
|
195
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
196
|
+
#
|
197
|
+
# TODO The API documentation says this method returns 403 if the path already exists, but it actually appends " (1)" to the end of the name and returns 200.
|
198
|
+
|
199
|
+
def create_folder(path, options={})
|
200
|
+
path.sub! /^\//, ''
|
201
|
+
path.sub! /\/$/, ''
|
202
|
+
begin
|
203
|
+
parse_metadata(post('fileops', 'create_folder', :path => Dropbox.check_path(path), :root => root(options), :ssl => @ssl)).to_struct_recursively
|
204
|
+
rescue UnsuccessfulResponseError => error
|
205
|
+
raise FileExistsError.new(path) if error.response.kind_of?(Net::HTTPForbidden)
|
206
|
+
raise error
|
207
|
+
end
|
208
|
+
end
|
209
|
+
alias :mkdir :create_folder
|
210
|
+
|
211
|
+
# Deletes a file or folder at the given path. The path is assumed to be
|
212
|
+
# relative to the configured mode's root.
|
213
|
+
#
|
214
|
+
# Raises FileNotFoundError if the file or folder does not exist at +path+.
|
215
|
+
#
|
216
|
+
# Options:
|
217
|
+
#
|
218
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
219
|
+
#
|
220
|
+
# TODO The API documentation says this method returns 404 if the path does not exist, but it actually returns 5xx.
|
221
|
+
|
222
|
+
def delete(path, options={})
|
223
|
+
path.sub! /^\//, ''
|
224
|
+
path.sub! /\/$/, ''
|
225
|
+
begin
|
226
|
+
api_response(:post, 'fileops', 'delete', :path => Dropbox.check_path(path), :root => root(options), :ssl => @ssl)
|
227
|
+
rescue UnsuccessfulResponseError => error
|
228
|
+
raise FileNotFoundError.new(path) if error.response.kind_of?(Net::HTTPNotFound)
|
229
|
+
raise error
|
230
|
+
end
|
231
|
+
return true
|
232
|
+
end
|
233
|
+
alias :rm :delete
|
234
|
+
|
235
|
+
# Moves the +source+ file to the path at +target+. If +target+ ends with a
|
236
|
+
# slash, the file name will remain unchanged. If +source+ and +target+ share
|
237
|
+
# the same path but have differing file names, the file will be renamed (see
|
238
|
+
# also the rename method). Returns a +Struct+ with metadata for the new
|
239
|
+
# file. (See the metadata method.)
|
240
|
+
#
|
241
|
+
# Both paths are assumed to be relative to the configured mode's root.
|
242
|
+
#
|
243
|
+
# Raises FileNotFoundError if +source+ does not exist. Raises
|
244
|
+
# FileExistsError if +target+ already exists.
|
245
|
+
#
|
246
|
+
# Options:
|
247
|
+
#
|
248
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
249
|
+
#
|
250
|
+
# TODO The API documentation says this method returns 404/403 if the source or target is invalid, but it actually returns 5xx.
|
251
|
+
|
252
|
+
def move(source, target, options={})
|
253
|
+
source.sub! /^\//, ''
|
254
|
+
target.sub! /^\//, ''
|
255
|
+
target << File.basename(source) if target.ends_with?('/')
|
256
|
+
begin
|
257
|
+
parse_metadata(post('fileops', 'move', :from_path => Dropbox.check_path(source), :to_path => Dropbox.check_path(target), :root => root(options), :ssl => @ssl)).to_struct_recursively
|
258
|
+
rescue UnsuccessfulResponseError => error
|
259
|
+
raise FileNotFoundError.new(source) if error.response.kind_of?(Net::HTTPNotFound)
|
260
|
+
raise FileExistsError.new(target) if error.response.kind_of?(Net::HTTPForbidden)
|
261
|
+
raise error
|
262
|
+
end
|
263
|
+
end
|
264
|
+
alias :mv :move
|
265
|
+
|
266
|
+
# Renames a file. Takes the same options and raises the same exceptions as
|
267
|
+
# the move method.
|
268
|
+
#
|
269
|
+
# Calling
|
270
|
+
#
|
271
|
+
# session.rename 'path/to/file', 'new_name'
|
272
|
+
#
|
273
|
+
# is equivalent to calling
|
274
|
+
#
|
275
|
+
# session.move 'path/to/file', 'path/to/new_name'
|
276
|
+
|
277
|
+
def rename(path, new_name, options={})
|
278
|
+
raise ArgumentError, "Names cannot have slashes in them" if new_name.include?('/')
|
279
|
+
path.sub! /\/$/, ''
|
280
|
+
destination = path.split('/')
|
281
|
+
destination[destination.size - 1] = new_name
|
282
|
+
destination = destination.join('/')
|
283
|
+
move path, destination, options
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns a cookie-protected URL that the authorized user can use to view
|
287
|
+
# the file at the given path. This URL requires an authorized user.
|
288
|
+
#
|
289
|
+
# The path is assumed to be relative to the configured mode's root.
|
290
|
+
#
|
291
|
+
# Options:
|
292
|
+
#
|
293
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
294
|
+
|
295
|
+
def link(path, options={})
|
296
|
+
path.sub! /^\//, ''
|
297
|
+
begin
|
298
|
+
rest = Dropbox.check_path(path).split('/')
|
299
|
+
rest << { :ssl => @ssl }
|
300
|
+
api_response(:get, 'links', root(options), *rest)
|
301
|
+
rescue UnsuccessfulResponseError => error
|
302
|
+
return error.response['Location'] if error.response.kind_of?(Net::HTTPFound)
|
303
|
+
#TODO shouldn't be using rescue blocks for normal program flow
|
304
|
+
raise error
|
305
|
+
end
|
306
|
+
end
|
307
|
+
memoize :link
|
308
|
+
|
309
|
+
# Returns a +Struct+ containing metadata on a given file or folder. The path
|
310
|
+
# is assumed to be relative to the configured mode's root.
|
311
|
+
#
|
312
|
+
# If you pass a directory for +path+, the metadata will also contain a
|
313
|
+
# listing of the directory contents (unless the +suppress_list+ option is
|
314
|
+
# true).
|
315
|
+
#
|
316
|
+
# For information on the schema of the return struct, see the Dropbox API
|
317
|
+
# at http://developers.dropbox.com/python/base.html#metadata
|
318
|
+
#
|
319
|
+
# The +modified+ key will be converted into a +Time+ instance. The +is_dir+
|
320
|
+
# key will also be available as <tt>directory?</tt>.
|
321
|
+
#
|
322
|
+
# Options:
|
323
|
+
#
|
324
|
+
# +suppress_list+:: Set this to true to remove the directory list from
|
325
|
+
# the result (only applicable if +path+ is a directory).
|
326
|
+
# +limit+:: Set this value to limit the number of entries returned when
|
327
|
+
# listing a directory. If the result has more than this number of
|
328
|
+
# entries, a TooManyEntriesError will be raised.
|
329
|
+
# +mode+:: Temporarily changes the API mode. See the MODES array.
|
330
|
+
#
|
331
|
+
# TODO hash option seems to return HTTPBadRequest for now
|
332
|
+
|
333
|
+
def metadata(path, options={})
|
334
|
+
path.sub! /^\//, ''
|
335
|
+
args = [
|
336
|
+
'metadata',
|
337
|
+
root(options)
|
338
|
+
]
|
339
|
+
args += Dropbox.check_path(path).split('/')
|
340
|
+
args << Hash.new
|
341
|
+
args.last[:file_limit] = options[:limit] if options[:limit]
|
342
|
+
#args.last[:hash] = options[:hash] if options[:hash]
|
343
|
+
args.last[:list] = !(options[:suppress_list].to_bool)
|
344
|
+
args.last[:ssl] = @ssl
|
345
|
+
|
346
|
+
begin
|
347
|
+
parse_metadata(get(*args)).to_struct_recursively
|
348
|
+
rescue UnsuccessfulResponseError => error
|
349
|
+
raise TooManyEntriesError.new(path) if error.response.kind_of?(Net::HTTPNotAcceptable)
|
350
|
+
raise FileNotFoundError.new(path) if error.response.kind_of?(Net::HTTPNotFound)
|
351
|
+
#return :not_modified if error.kind_of?(Net::HTTPNotModified)
|
352
|
+
raise error
|
353
|
+
end
|
354
|
+
end
|
355
|
+
memoize :metadata
|
356
|
+
alias :info :metadata
|
357
|
+
|
358
|
+
# Returns an array of <tt>Struct</tt>s with information on each file within
|
359
|
+
# the given directory. Calling
|
360
|
+
#
|
361
|
+
# session.list 'my/folder'
|
362
|
+
#
|
363
|
+
# is equivalent to calling
|
364
|
+
#
|
365
|
+
# session.metadata('my/folder').contents
|
366
|
+
#
|
367
|
+
# Returns nil if the path is not a directory. Raises the same exceptions as
|
368
|
+
# the metadata method. Takes the same options as the metadata method, except
|
369
|
+
# the +suppress_list+ option is implied to be false.
|
370
|
+
|
371
|
+
|
372
|
+
def list(path, options={})
|
373
|
+
metadata(path, options.merge(:suppress_list => false)).contents
|
374
|
+
end
|
375
|
+
alias :ls :list
|
376
|
+
|
377
|
+
def event_metadata(target_events, options={}) # :nodoc:
|
378
|
+
get 'event_metadata', :ssl => @ssl, :root => root(options), :target_events => target_events
|
379
|
+
end
|
380
|
+
|
381
|
+
def event_content(entry, options={}) # :nodoc:
|
382
|
+
request = Dropbox.api_url('event_content', :target_event => entry, :ssl => @ssl, :root => root(options))
|
383
|
+
response = api_internal(:get, request)
|
384
|
+
begin
|
385
|
+
return response.body, JSON.parse(response.header['X-Dropbox-Metadata'])
|
386
|
+
rescue JSON::ParserError
|
387
|
+
raise ParseError.new(request, response)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns the configured API mode.
|
392
|
+
|
393
|
+
def mode
|
394
|
+
@api_mode ||= :sandbox
|
395
|
+
end
|
396
|
+
|
397
|
+
# Sets the API mode. See the MODES array.
|
398
|
+
|
399
|
+
def mode=(newmode)
|
400
|
+
raise ArgumentError, "Unknown API mode #{newmode.inspect}" unless MODES.include?(newmode)
|
401
|
+
@api_mode = newmode
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
def parse_metadata(hsh)
|
407
|
+
hsh[:modified] = Time.parse(hsh[:modified]) if hsh[:modified]
|
408
|
+
hsh[:directory?] = hsh[:is_dir]
|
409
|
+
hsh.each { |_,v| parse_metadata(v) if v.kind_of?(Hash) }
|
410
|
+
hsh.each { |_,v| v.each { |h| parse_metadata(h) if h.kind_of?(Hash) } if v.kind_of?(Array) }
|
411
|
+
hsh
|
412
|
+
end
|
413
|
+
|
414
|
+
def root(options={})
|
415
|
+
api_mode = options[:mode] || mode
|
416
|
+
raise ArgumentError, "Unknown API mode #{api_mode.inspect}" unless MODES.include?(api_mode)
|
417
|
+
return api_mode == :sandbox ? 'sandbox' : 'dropbox'
|
418
|
+
end
|
419
|
+
|
420
|
+
def get(*params)
|
421
|
+
api_json :get, *params
|
422
|
+
end
|
423
|
+
|
424
|
+
def post(*params)
|
425
|
+
api_json :post, *params
|
426
|
+
end
|
427
|
+
|
428
|
+
def api_internal(method, request)
|
429
|
+
raise UnauthorizedError, "Must authorize before you can use API method" unless @access_token
|
430
|
+
response = @access_token.send(method, request)
|
431
|
+
raise UnsuccessfulResponseError.new(request, response) unless response.kind_of?(Net::HTTPSuccess)
|
432
|
+
return response
|
433
|
+
end
|
434
|
+
|
435
|
+
def api_json(method, *params)
|
436
|
+
request = Dropbox.api_url(*params)
|
437
|
+
response = api_internal(method, request)
|
438
|
+
begin
|
439
|
+
return JSON.parse(response.body).symbolize_keys_recursively
|
440
|
+
rescue JSON::ParserError
|
441
|
+
raise ParseError.new(request, response)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def api_body(method, *params)
|
446
|
+
api_response(method, *params).body
|
447
|
+
end
|
448
|
+
|
449
|
+
def api_response(method, *params)
|
450
|
+
api_internal(method, Dropbox.api_url(*params))
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# Superclass for exceptions raised when the server reports an error.
|
455
|
+
|
456
|
+
class APIError < StandardError
|
457
|
+
# The request URL.
|
458
|
+
attr_reader :request
|
459
|
+
# The Net::HTTPResponse returned by the server.
|
460
|
+
attr_reader :response
|
461
|
+
|
462
|
+
def initialize(request, response) # :nodoc:
|
463
|
+
@request = request
|
464
|
+
@response = response
|
465
|
+
end
|
466
|
+
|
467
|
+
def to_s # :nodoc:
|
468
|
+
"API error: #{request}"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
# Raised when the Dropbox API returns a response that was not understood.
|
473
|
+
|
474
|
+
class ParseError < APIError
|
475
|
+
def to_s # :nodoc:
|
476
|
+
"Invalid response received: #{request}"
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
# Raised when something other than 200 OK is returned by an API method.
|
481
|
+
|
482
|
+
class UnsuccessfulResponseError < APIError
|
483
|
+
def to_s # :nodoc:
|
484
|
+
"HTTP status #{@response.class.to_s} received: #{request}"
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
# Superclass of errors relating to Dropbox files.
|
489
|
+
|
490
|
+
class FileError < StandardError
|
491
|
+
# The path of the offending file.
|
492
|
+
attr_reader :path
|
493
|
+
|
494
|
+
def initialize(path) # :nodoc:
|
495
|
+
@path = path
|
496
|
+
end
|
497
|
+
|
498
|
+
def to_s # :nodoc:
|
499
|
+
"#{self.class.to_s}: #{@path}"
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Raised when a Dropbox file doesn't exist.
|
504
|
+
|
505
|
+
class FileNotFoundError < FileError; end
|
506
|
+
|
507
|
+
# Raised when a Dropbox file is in the way.
|
508
|
+
|
509
|
+
class FileExistsError < FileError; end
|
510
|
+
|
511
|
+
# Raised when the number of files within a directory exceeds a specified
|
512
|
+
# limit.
|
513
|
+
|
514
|
+
class TooManyEntriesError < FileError; end
|
515
|
+
|
516
|
+
# Raised when the event_metadata method returns an error.
|
517
|
+
|
518
|
+
class PingbackError < StandardError
|
519
|
+
# The HTTP error code returned by the event_metadata method.
|
520
|
+
attr_reader :code
|
521
|
+
|
522
|
+
def initialize(code) # :nodoc
|
523
|
+
@code = code
|
524
|
+
end
|
525
|
+
|
526
|
+
def to_s # :nodoc:
|
527
|
+
"#{self.class.to_s} code #{@code}"
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Defines the Dropbox::Entry class.
|
2
|
+
|
3
|
+
nil # doc fix
|
4
|
+
|
5
|
+
module Dropbox
|
6
|
+
|
7
|
+
# A façade over a Dropbox::Session that allows the programmer to interact with
|
8
|
+
# Dropbox files in an object-oriented manner. The Dropbox::Entry instance is
|
9
|
+
# created by calling the Dropbox::API#entry method:
|
10
|
+
#
|
11
|
+
# file = session.file('remote/file.pdf')
|
12
|
+
# dir = session.directory('remote/dir') # these calls are actually identical
|
13
|
+
#
|
14
|
+
# Note that no network calls are made; this merely creates a façade that will
|
15
|
+
# delegate future calls to the session:
|
16
|
+
#
|
17
|
+
# file.move('new/path') # identical to calling session.move('remote/file.pdf', 'new/path')
|
18
|
+
#
|
19
|
+
# The internal path is updated as the file is moved and renamed:
|
20
|
+
#
|
21
|
+
# file = session.file('first_name.txt')
|
22
|
+
# file.rename('second_name.txt')
|
23
|
+
# file.rename('third_name.txt') # works as the internal path is updated with the first rename
|
24
|
+
|
25
|
+
class Entry
|
26
|
+
# The remote path of the file.
|
27
|
+
attr_reader :path
|
28
|
+
|
29
|
+
def initialize(session, path) # :nodoc:
|
30
|
+
@session = session
|
31
|
+
@path = path
|
32
|
+
end
|
33
|
+
|
34
|
+
# Delegates to Dropbox::API#metadata.
|
35
|
+
|
36
|
+
def metadata(options={})
|
37
|
+
@session.metadata path, options
|
38
|
+
end
|
39
|
+
alias :info :metadata
|
40
|
+
|
41
|
+
# Delegates to Dropbox::API#list
|
42
|
+
|
43
|
+
def list(options={})
|
44
|
+
@session.list path, options
|
45
|
+
end
|
46
|
+
alias :ls :list
|
47
|
+
|
48
|
+
# Delegates to Dropbox::API#move.
|
49
|
+
|
50
|
+
def move(dest, options={})
|
51
|
+
result = @session.move(path, dest, options)
|
52
|
+
@path = result.path.gsub(/^\//, '')
|
53
|
+
return result
|
54
|
+
end
|
55
|
+
alias :mv :move
|
56
|
+
|
57
|
+
# Delegates to Dropbox::API#rename.
|
58
|
+
|
59
|
+
def rename(name, options={})
|
60
|
+
result = @session.rename(path, name, options)
|
61
|
+
@path = result.path.gsub(/^\//, '')
|
62
|
+
return result
|
63
|
+
end
|
64
|
+
|
65
|
+
# Delegates to Dropbox::API#copy.
|
66
|
+
|
67
|
+
def copy(dest, options={})
|
68
|
+
@session.copy path, dest, options
|
69
|
+
end
|
70
|
+
alias :cp :copy
|
71
|
+
|
72
|
+
# Delegates to Dropbox::API#delete.
|
73
|
+
|
74
|
+
def delete(options={})
|
75
|
+
@session.delete path, options
|
76
|
+
end
|
77
|
+
alias :rm :delete
|
78
|
+
|
79
|
+
# Delegates to Dropbox::API#download.
|
80
|
+
|
81
|
+
def download(options={})
|
82
|
+
@session.download path, options
|
83
|
+
end
|
84
|
+
alias :body :download
|
85
|
+
|
86
|
+
# Delegates to Dropbox::API#link.
|
87
|
+
|
88
|
+
def link(options={})
|
89
|
+
@session.link path, options
|
90
|
+
end
|
91
|
+
|
92
|
+
def inspect # :nodoc:
|
93
|
+
"#<#{self.class.to_s} #{path}>"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|