Jesus / dropbox_api

Ruby client library for Dropbox API v2
MIT License
171 stars 113 forks source link

Chunked downloads #8

Open TrevorHinesley opened 7 years ago

TrevorHinesley commented 7 years ago

For getting large files from a user's Dropbox, their API allows you to specify a range of bytes--is this possible in the current dropbox_api gem's DSL?

Jesus commented 7 years ago

Sorry for the late reply.

I couldn't find the option to download a specific range of bytes anywhere in their API documentation. Could you link to any resource related to this?

If this is actually possible I'd be interested in implementing this feature.

TrevorHinesley commented 7 years ago

https://www.dropbox.com/developers-v1/core/docs#files-GET Look at the Notes section

Jesus commented 7 years ago

That link is for Dropbox API v1, this library implements API v2.

However, I could find this in the docs from v2:

As with content-upload endpoints, arguments are passed in the Dropbox-API-Arg request header or arg URL parameter. The response body contains file content, so the result will appear as JSON in the Dropbox-API-Result response header. These endpoints are also on the content.dropboxapi.com domain. These endpoints also support HTTP GET along with ETag-based caching (If-None-Match) and HTTP range requests.

Source: https://www.dropbox.com/developers/documentation/http/documentation#formats

So, you're right and it should be possible to download a range of bytes. I'm not sure yet about the details on how this is meant to work.

I'll see if we can have support for this feature soon.

apurvis commented 6 years ago

https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests

gbt5 commented 6 years ago

Here's my monkeypatch:

module DropboxApi::Endpoints
  class Base
    def process_response(raw_response)
      # Official Dropbox documentation for HTTP error codes:
      # https://www.dropbox.com/developers/documentation/http/documentation#error-handling
      case raw_response.status
      when 200, 206, 409
        # Status code 409 is "Endpoint-specific error". We need to look at
        # the response body to build an exception.
        build_result(raw_response.env[:api_result])
      when 429
        error = DropboxApi::Errors::TooManyRequestsError.build(
          raw_response.env[:api_result]["error_summary"],
          raw_response.env[:api_result]["error"]["reason"]
        )

        error.retry_after = raw_response.headers["retry-after"].to_i

        raise error
      else
        raise DropboxApi::Errors::HttpError,
          "HTTP #{raw_response.status}: #{raw_response.body}"
      end
    end
  end

  class ContentChunkedDownload < DropboxApi::Endpoints::Base
    def initialize(builder)
      @connection = builder.build("https://content.dropboxapi.com") do |c|
        c.response :decode_result
      end
    end

    def build_request(params)
      body = nil

      range_from = params.delete(:range_from)
      range_to   = params.delete(:range_to)
      range = "bytes=#{range_from}-#{range_to}" unless range_from.nil? || range_from.nil?

      headers = {
        'Dropbox-API-Arg' => JSON.dump(params),
        'Content-Type' => ''
      }

      headers.merge!({ 'Range' => range }) unless range.nil?

      return body, headers
    end

    def perform_request(params)
      response = get_response(params)
      api_result = process_response response

      # TODO: Stream response, current implementation will fail with very large
      #       files.
      yield response.body if block_given?

      api_result
    end
  end
end

module DropboxApi::Endpoints::Files
  class DownloadChunked < DropboxApi::Endpoints::ContentChunkedDownload
    Method      = :post
    Path        = "/2/files/download".freeze
    ResultType  = DropboxApi::Metadata::File
    ErrorType   = DropboxApi::Errors::DownloadError

    # Download a file from a users Dropbox.
    #
    # @param path [String] The path of the file to download.
    add_endpoint :download_chunk do |path, from, to, &block|
      perform_request({:path => path}.merge(range_from: from, range_to: to), &block)
    end
  end
end