Class: Dbox::API

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/dbox/api.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Loggable

included, #log

Constructor Details

#initializeAPI

Returns a new instance of API.



54
55
# File 'lib/dbox/api.rb', line 54

def initialize
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



50
51
52
# File 'lib/dbox/api.rb', line 50

def client
  @client
end

Class Method Details

.authorizeObject

Raises:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/dbox/api.rb', line 14

def self.authorize
  app_key = ENV["DROPBOX_APP_KEY"]
  app_secret = ENV["DROPBOX_APP_SECRET"]
  
  raise(ConfigurationError, "Please set the DROPBOX_APP_KEY environment variable to a Dropbox application key") unless app_key
  raise(ConfigurationError, "Please set the DROPBOX_APP_SECRET environment variable to a Dropbox application secret") unless app_secret


  flow = DropboxOAuth2FlowNoRedirect.new(app_key, app_secret)
  authorize_url = flow.start()

  puts '1. Go to: ' + authorize_url
  puts '2. Click "Allow" (you might have to log in first)'
  puts '3. Copy the authorization code'
  print 'Enter the authorization code here: '
  code = STDIN.readline.strip

  # This will fail if the user gave us an invalid authorization code
  access_token, user_id = flow.finish(code)
  
  puts "export DROPBOX_ACCESS_TOKEN=#{access_token}"
  puts "export DROPBOX_USER_ID=#{user_id}"
  puts
  puts "This auth token will last for 10 years, or when you choose to invalidate it, whichever comes first."
  puts
  puts "Now either include these constants in yours calls to dbox, or set them as environment variables."
  puts "In bash, including them in calls looks like:"
  puts "$ DROPBOX_ACCESS_TOKEN=#{access_token} DROPBOX_USER_ID=#{user_id} dbox ..."
end

.connectObject



44
45
46
47
48
# File 'lib/dbox/api.rb', line 44

def self.connect
  api = new()
  api.connect
  api
end

Instance Method Details

#connectObject

Raises:



61
62
63
64
65
66
67
68
# File 'lib/dbox/api.rb', line 61

def connect
  access_token = ENV["DROPBOX_ACCESS_TOKEN"]
  access_type = ENV["DROPBOX_ACCESS_TYPE"] || "dropbox"
  
  raise(ConfigurationError, "Please set the DROPBOX_ACCESS_TOKEN environment variable to a Dropbox access token") unless access_token
  raise(ConfigurationError, "Please set the DROPBOX_ACCESS_TYPE environment variable either dropbox (full access) or sandbox (App access)") unless access_type == "dropbox" || access_type == "app_folder"
  @client = DropboxClient.new(access_token)
end

#create_dir(path) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/dbox/api.rb', line 133

def create_dir(path)
  run(path) do
    log.info "Creating #{path}"
    begin
      @client.file_create_folder(path)
    rescue DropboxError => e
      if e.http_response.kind_of?(Net::HTTPForbidden)
        raise RemoteAlreadyExists, "Either the directory at #{path} already exists, or it has invalid characters in the name"
      else
        raise e
      end
    end
  end
end

#delete_dir(path) ⇒ Object



148
149
150
151
152
153
# File 'lib/dbox/api.rb', line 148

def delete_dir(path)
  run(path) do
    log.info "Deleting #{path}"
    @client.file_delete(path)
  end
end

#delete_file(path) ⇒ Object



189
190
191
192
193
194
# File 'lib/dbox/api.rb', line 189

def delete_file(path)
  run(path) do
    log.info "Deleting #{path}"
    @client.file_delete(path)
  end
end

#get_file(path, file_obj, stream = false) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/dbox/api.rb', line 155

def get_file(path, file_obj, stream=false)
  unless stream
    # just download directly using the get_file API
    res = run(path) do
      log.info "Downloading #{path}"
      @client.get_file(path)
    end
    if res.kind_of?(String)
      file_obj << res
      true
    else
      raise DropboxError.new("Invalid response #{res.inspect}")
    end
  else
    # use the media API to get a URL that we can stream from, and
    # then stream the file to disk
    res = run(path) { @client.media(path) }
    url = res[:url] if res && res.kind_of?(Hash)
    if url
      log.info "Downloading #{path}"
      streaming_download(url, file_obj)
    else
      get_file(path, file_obj, false)
    end
  end
end

#handle_response(path, res, &else_proc) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/dbox/api.rb', line 104

def handle_response(path, res, &else_proc)
  case res
  when Hash
    InsensitiveHash[res]
  when String
    res
  when Net::HTTPNotFound
    raise RemoteMissing, "#{path} does not exist on Dropbox"
  when Net::HTTPForbidden
    raise RequestDenied, "Operation on #{path} denied"
  when Net::HTTPNotModified
    :not_modified
  when true
    true
  else
    else_proc.call()
  end
end

#initialize_copy(other) ⇒ Object



57
58
59
# File 'lib/dbox/api.rb', line 57

def initialize_copy(other)
  @client = other.client.clone()
end

#metadata(path = "/", hash = nil, list = true) ⇒ Object



123
124
125
126
127
128
129
130
131
# File 'lib/dbox/api.rb', line 123

def (path = "/", hash = nil, list=true)
  run(path) do
    log.debug "Fetching metadata for #{path}"
    res = @client.(path, 10000, list, hash)
    log.debug res.inspect
    raise Dbox::RemoteMissing, "#{path} has been deleted on Dropbox" if res["is_deleted"]
    res
  end
end

#move(old_path, new_path) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/dbox/api.rb', line 196

def move(old_path, new_path)
  run(old_path) do
    log.info "Moving #{old_path} to #{new_path}"
    begin
      @client.file_move(old_path, new_path)
    rescue DropboxError => e
      if e.http_response.kind_of?(Net::HTTPForbidden)
        raise RemoteAlreadyExists, "Error during move -- there may already be a Dropbox folder at #{new_path}"
      else
        raise e
      end
    end
  end
end

#put_file(path, local_path, previous_revision = nil) ⇒ Object



182
183
184
185
186
187
# File 'lib/dbox/api.rb', line 182

def put_file(path, local_path, previous_revision=nil)
  run(path) do
    log.info "Uploading #{path}"
    File.open(local_path, "r") {|f| @client.put_file(path, f, false, previous_revision) }
  end
end

#run(path, tries = NUM_TRIES, &proc) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/dbox/api.rb', line 70

def run(path, tries = NUM_TRIES, &proc)
  begin
    res = proc.call
    handle_response(path, res) { raise RuntimeError, "Unexpected result: #{res.inspect}" }
  rescue DropboxNotModified => e
    :not_modified
  rescue DropboxAuthError => e
    raise e
  rescue DropboxError => e
    if tries > 0
      if e.http_response.kind_of?(Net::HTTPServiceUnavailable)
        log.info "Encountered 503 on #{path} (likely rate limiting). Sleeping #{TIME_BETWEEN_TRIES}s and trying again."
        # TODO check for "Retry-After" header and use that for sleep instead of TIME_BETWEEN_TRIES
        log.info "Headers: #{e.http_response.to_hash.inspect}"
      else
        log.info "Encountered a dropbox error. Sleeping #{TIME_BETWEEN_TRIES}s and trying again. Error: #{e.inspect}"
        log.info "Headers: #{e.http_response.to_hash.inspect}"
      end
      sleep TIME_BETWEEN_TRIES
      run(path, tries - 1, &proc)
    else
      handle_response(path, e.http_response) { raise ServerError, "Server error -- might be a hiccup, please try your request again (#{e.message})" }
    end
  rescue => e
    if tries > 0
      log.info "Encounted an unknown error. Sleeping #{TIME_BETWEEN_TRIES}s and trying again. Error: #{e.inspect}"
      sleep TIME_BETWEEN_TRIES
      run(path, tries - 1, &proc)
    else
      raise e
    end
  end
end

#streaming_download(url, io, num_redirects = 0) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/dbox/api.rb', line 211

def streaming_download(url, io, num_redirects = 0)
  url = URI.parse(url)
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  http.ca_file = Dropbox::TRUSTED_CERT_FILE

  req = Net::HTTP::Get.new(url.request_uri)
  req["User-Agent"] = "OfficialDropboxRubySDK/#{Dropbox::SDK_VERSION}"

  http.request(req) do |res|
    if res.kind_of?(Net::HTTPSuccess)
      # stream into given io
      res.read_body {|chunk| io.write(chunk) }
      true
    else
      if res.kind_of?(Net::HTTPRedirection) && res.header['location'] && num_redirects < 10
        log.info("following redirect, num_redirects = #{num_redirects}")
        log.info("redirect url: #{res.header['location']}")
        streaming_download(res.header['location'], io, num_redirects + 1)
      else
        raise DropboxError.new("Invalid response #{res.inspect}")
      end
    end
  end
end