Skip to content
This repository has been archived by the owner on Dec 11, 2020. It is now read-only.

Commit

Permalink
Simple interface
Browse files Browse the repository at this point in the history
- a Client instance can create a tranfer (and add files on the go)
- split Transfers' .new and .create,
  - new only initializes,
  - create also sends it to WeTransfers' Public API
  - need to have a seperate add_file method to make this work
- MiniIO introduced to get wrap all things IO, in a very slim interface
  • Loading branch information
arnoFleming committed Feb 22, 2019
1 parent 5387a3f commit dfd22cf
Show file tree
Hide file tree
Showing 32 changed files with 707 additions and 148 deletions.
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.6.1
82 changes: 82 additions & 0 deletions lib/we_transfer/communication_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module WeTransfer
class CommunicationError < StandardError; end
module CommunicationHelper
API_URI_BASE = 'https://dev.wetransfer.com'.freeze

class << self
attr_accessor :logger, :api_key
end

def logger
@logger ||= CommunicationHelper.logger
end

def api_key
@api_key ||= CommunicationHelper.api_key
end

def request_as
authorize_if_no_bearer_token!

@request_as ||= Faraday.new(API_URI_BASE) do |c|
c.response :logger, logger
c.adapter Faraday.default_adapter
c.headers = auth_headers.merge(
"User-Agent" => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}",
"Content-Type" => "application/json",
)
end
end

private

def auth_headers
authorize_if_no_bearer_token!

{
'X-API-Key' => api_key,
'Authorization' => ('Bearer %s' % @bearer_token),
}
end

def ensure_ok_status!(response)
case response.status
when 200..299
true
when 400..499
logger.error response
raise WeTransfer::CommunicationError, JSON.parse(response.body)["message"]
when 500..504
logger.error response
raise WeTransfer::CommunicationError, "Response had a #{response.status} code, we could retry"
else
logger.error response
raise WeTransfer::CommunicationError, "Response had a #{response.status} code, no idea what to do with that"
end
end

def authorize_if_no_bearer_token!
return @bearer_token if @bearer_token

response = Faraday.new(API_URI_BASE) do |c|
c.response :logger, logger
c.adapter Faraday.default_adapter
c.headers = {
"User-Agent" => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}",
"Content-Type" => "application/json",
}
end.post(
'/v2/authorize',
'',
'Content-Type' => 'application/json',
'X-API-Key' => CommunicationHelper.api_key,
)
ensure_ok_status!(response)
bearer_token = JSON.parse(response.body)['token']
if bearer_token.nil? || bearer_token.empty?
raise WeTransfer::CommunicationError, "The authorization call returned #{response.body} and no usable :token key could be found there"
end
@bearer_token = bearer_token
end
end
end
47 changes: 47 additions & 0 deletions lib/we_transfer/mini_io.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module WeTransfer
# Wrapper around an IO object, delegating only methods we need for creating
# and sending a file in chunks.
#
class MiniIO
# Initialize with an io object to wrap
#
# @param io [anything] An object that responds to #read, #rewind, #seek, #size
#
def initialize(io)
@io = io
end

def read(*args)
@io.read(*args)
end

def rewind
@io.rewind
end

def seek(*args)
@io.seek(*args)
end

# The size delegated to io.
# If io is nil, we return nil.
#
# nil is fine, since this method is used only as the default size for a
# WeTransferFile
# @returns [Integer, nil] the size of the io. See IO#size
#
def size
@io&.size
end

# The name of the io, guessed using File.basename. If this raises a TypeError
# we swallow the error, since this is used only as the default name for a
# WeTransferFile
#
def name
File.basename(@io)
rescue TypeError
# yeah, what?
end
end
end
66 changes: 66 additions & 0 deletions lib/we_transfer/transfer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module WeTransfer
class Transfer
class DuplicateFileNameError < ArgumentError; end
class NoFilesAddedError < StandardError; end

include CommunicationHelper

def self.create(message:, &block)
transfer = new(message: message)

transfer.persist(&block)
end

def initialize(message:)
@message = message
@files = []
@unique_file_names = Set.new
end

# Add files (if still needed)
def persist
yield(self) if block_given?

create_remote_transfer

## files should now be in persisted status

end

# Add one or more files to a transfer, so a transfer can be created over the
# WeTransfer public API
#
# @param name [String] (nil) the name of the file
#
# @return [WeTransfer::Client]
def add_file(name: nil, size: nil, io: nil)
file = WeTransferFile.new(name: name, size: size, io: io)
raise DuplicateFileNameError unless @unique_file_names.add?(file.name.downcase)

@files << file
self
end

private

def as_json_request_params
{
message: @message,
files: @files.map(&:as_json_request_params),
}
end

def create_remote_transfer
raise NoFilesAddedError if @unique_file_names.empty?

response = request_as.post(
'/v2/transfers',
as_json_request_params.to_json,
{}
)
ensure_ok_status!(response)

@remote_transfer = RemoteTransfer.new(JSON.parse(response.body, symbolize_names: true))
end
end
end
23 changes: 23 additions & 0 deletions lib/we_transfer/we_transfer_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module WeTransfer
class WeTransferFile

attr_reader :name

def initialize(name: nil, size: nil, io: nil)
@io = io.is_a?(MiniIO) ? io : MiniIO.new(io)
@name = name || @io.name
@size = size || @io.size

raise ArgumentError, "Need a file name and a size, or io should provide it" unless @name && @size
end

def as_json_request_params
{
name: @name,
size: @size,
}
end

# def persist()
end
end
126 changes: 53 additions & 73 deletions lib/we_transfer_client.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'faraday'
require 'logger'
require 'json'
Expand All @@ -16,93 +18,71 @@
require_relative 'we_transfer_client/transfers'
require_relative 'we_transfer_client/boards'

%w[communication_helper transfer mini_io we_transfer_file ].each do |file|
require_relative "we_transfer/#{file}"
end

module WeTransfer
class Client
include WeTransfer::Client::Transfers
include WeTransfer::Client::Boards
include CommunicationHelper

class Error < StandardError
end
class Error < StandardError; end
NullLogger = Logger.new(nil)

NULL_LOGGER = Logger.new(nil)
API_URL_BASE = 'https://dev.wetransfer.com'

def initialize(api_key:, logger: NULL_LOGGER)
@api_url_base = 'https://dev.wetransfer.com'
@api_key = api_key.to_str
# include WeTransfer::Client::Transfers
# include WeTransfer::Client::Boards

## initialize a WeTransfer::Client
#
# @param api_key [String] The API key you want to authenticate with
# @param logger [Logger] (NullLogger) your custom logger
#
# @return [WeTransfer::Client]
def initialize(api_key:, logger: NullLogger)
CommunicationHelper.api_key = api_key
@bearer_token = nil
@logger = logger
CommunicationHelper.logger = logger
end

def upload_file(object:, file:, io:)
put_io_in_parts(object: object, file: file, io: io)
end

def complete_file!(object:, file:)
object.prepare_file_completion(client: self, file: file)
end
def create_transfer(**args, &block)
transfer = WeTransfer::Transfer.new(args, &block)
@transfer = transfer

def check_for_file_duplicates(files, new_file)
if files.select { |file| file.name == new_file.name }.size != 1
raise ArgumentError, 'Duplicate file entry'
end
# TODO: Either we have an accessor for transfer, or we're not returning self - the transfer is unavailable otherwise
self
end

def put_io_in_parts(object:, file:, io:)
(1..file.multipart.part_numbers).each do |part_n_one_based|
upload_url, chunk_size = object.prepare_file_upload(client: self, file: file, part_number: part_n_one_based)
part_io = StringIO.new(io.read(chunk_size))
part_io.rewind
response = faraday.put(
upload_url,
part_io,
'Content-Type' => 'binary/octet-stream',
'Content-Length' => part_io.size.to_s
)
ensure_ok_status!(response)
end
{success: true, message: 'File Uploaded'}
end
# def upload_file(object:, file:, io:)
# put_io_in_parts(object: object, file: file, io: io)
# end

def faraday
Faraday.new(@api_url_base) do |c|
c.response :logger, @logger
c.adapter Faraday.default_adapter
c.headers = { 'User-Agent' => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}"}
end
end
# def complete_file!(object:, file:)
# object.prepare_file_completion(client: self, file: file)
# end

def authorize_if_no_bearer_token!
return if @bearer_token
response = faraday.post('/v2/authorize', '{}', 'Content-Type' => 'application/json', 'X-API-Key' => @api_key)
ensure_ok_status!(response)
@bearer_token = JSON.parse(response.body, symbolize_names: true)[:token]
if @bearer_token.nil? || @bearer_token.empty?
raise Error, "The authorization call returned #{response.body} and no usable :token key could be found there"
end
end
# def check_for_file_duplicates(files, new_file)
# if files.select { |file| file.name == new_file.name }.size != 1
# raise ArgumentError, 'Duplicate file entry'
# end
# end

def auth_headers
raise 'No bearer token retrieved yet' unless @bearer_token
{
'X-API-Key' => @api_key,
'Authorization' => ('Bearer %s' % @bearer_token),
}
end

def ensure_ok_status!(response)
case response.status
when 200..299
true
when 400..499
@logger.error response
raise Error, "Response had a #{response.status} code, the server will not accept this request even if retried"
when 500..504
@logger.error response
raise Error, "Response had a #{response.status} code, we could retry"
else
@logger.error response
raise Error, "Response had a #{response.status} code, no idea what to do with that"
end
end
# def put_io_in_parts(object:, file:, io:)
# (1..file.multipart.part_numbers).each do |part_n_one_based|
# upload_url, chunk_size = object.prepare_file_upload(client: self, file: file, part_number: part_n_one_based)
# part_io = StringIO.new(io.read(chunk_size))
# part_io.rewind
# response = request_as.put(
# upload_url,
# part_io,
# 'Content-Type' => 'binary/octet-stream',
# 'Content-Length' => part_io.size.to_s
# )
# ensure_ok_status!(response)
# end
# {success: true, message: 'File Uploaded'}
# end
end
end
6 changes: 3 additions & 3 deletions lib/we_transfer_client/boards.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def create_feature_board(name:, description:, future_board_class: FutureBoard, b

def create_remote_board(board:, remote_board_class: RemoteBoard)
authorize_if_no_bearer_token!
response = faraday.post(
response = request_as.post(
'/v2/boards',
JSON.pretty_generate(board.to_initial_request_params),
JSON.generate(board.to_initial_request_params),
auth_headers.merge('Content-Type' => 'application/json')
)
ensure_ok_status!(response)
Expand All @@ -61,7 +61,7 @@ def add_items_to_remote_board(items:, remote_board:)

def request_board(board:, remote_board_class: RemoteBoard)
authorize_if_no_bearer_token!
response = faraday.get(
response = request_as.get(
"/v2/boards/#{board.id}",
{},
auth_headers.merge('Content-Type' => 'application/json')
Expand Down
Loading

0 comments on commit dfd22cf

Please sign in to comment.