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

Commit dfd22cf

Browse files
committed
Simple interface
- 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
1 parent 5387a3f commit dfd22cf

32 files changed

+707
-148
lines changed

.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2.6.1
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
module WeTransfer
2+
class CommunicationError < StandardError; end
3+
module CommunicationHelper
4+
API_URI_BASE = 'https://dev.wetransfer.com'.freeze
5+
6+
class << self
7+
attr_accessor :logger, :api_key
8+
end
9+
10+
def logger
11+
@logger ||= CommunicationHelper.logger
12+
end
13+
14+
def api_key
15+
@api_key ||= CommunicationHelper.api_key
16+
end
17+
18+
def request_as
19+
authorize_if_no_bearer_token!
20+
21+
@request_as ||= Faraday.new(API_URI_BASE) do |c|
22+
c.response :logger, logger
23+
c.adapter Faraday.default_adapter
24+
c.headers = auth_headers.merge(
25+
"User-Agent" => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}",
26+
"Content-Type" => "application/json",
27+
)
28+
end
29+
end
30+
31+
private
32+
33+
def auth_headers
34+
authorize_if_no_bearer_token!
35+
36+
{
37+
'X-API-Key' => api_key,
38+
'Authorization' => ('Bearer %s' % @bearer_token),
39+
}
40+
end
41+
42+
def ensure_ok_status!(response)
43+
case response.status
44+
when 200..299
45+
true
46+
when 400..499
47+
logger.error response
48+
raise WeTransfer::CommunicationError, JSON.parse(response.body)["message"]
49+
when 500..504
50+
logger.error response
51+
raise WeTransfer::CommunicationError, "Response had a #{response.status} code, we could retry"
52+
else
53+
logger.error response
54+
raise WeTransfer::CommunicationError, "Response had a #{response.status} code, no idea what to do with that"
55+
end
56+
end
57+
58+
def authorize_if_no_bearer_token!
59+
return @bearer_token if @bearer_token
60+
61+
response = Faraday.new(API_URI_BASE) do |c|
62+
c.response :logger, logger
63+
c.adapter Faraday.default_adapter
64+
c.headers = {
65+
"User-Agent" => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}",
66+
"Content-Type" => "application/json",
67+
}
68+
end.post(
69+
'/v2/authorize',
70+
'',
71+
'Content-Type' => 'application/json',
72+
'X-API-Key' => CommunicationHelper.api_key,
73+
)
74+
ensure_ok_status!(response)
75+
bearer_token = JSON.parse(response.body)['token']
76+
if bearer_token.nil? || bearer_token.empty?
77+
raise WeTransfer::CommunicationError, "The authorization call returned #{response.body} and no usable :token key could be found there"
78+
end
79+
@bearer_token = bearer_token
80+
end
81+
end
82+
end

lib/we_transfer/mini_io.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module WeTransfer
2+
# Wrapper around an IO object, delegating only methods we need for creating
3+
# and sending a file in chunks.
4+
#
5+
class MiniIO
6+
# Initialize with an io object to wrap
7+
#
8+
# @param io [anything] An object that responds to #read, #rewind, #seek, #size
9+
#
10+
def initialize(io)
11+
@io = io
12+
end
13+
14+
def read(*args)
15+
@io.read(*args)
16+
end
17+
18+
def rewind
19+
@io.rewind
20+
end
21+
22+
def seek(*args)
23+
@io.seek(*args)
24+
end
25+
26+
# The size delegated to io.
27+
# If io is nil, we return nil.
28+
#
29+
# nil is fine, since this method is used only as the default size for a
30+
# WeTransferFile
31+
# @returns [Integer, nil] the size of the io. See IO#size
32+
#
33+
def size
34+
@io&.size
35+
end
36+
37+
# The name of the io, guessed using File.basename. If this raises a TypeError
38+
# we swallow the error, since this is used only as the default name for a
39+
# WeTransferFile
40+
#
41+
def name
42+
File.basename(@io)
43+
rescue TypeError
44+
# yeah, what?
45+
end
46+
end
47+
end

lib/we_transfer/transfer.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module WeTransfer
2+
class Transfer
3+
class DuplicateFileNameError < ArgumentError; end
4+
class NoFilesAddedError < StandardError; end
5+
6+
include CommunicationHelper
7+
8+
def self.create(message:, &block)
9+
transfer = new(message: message)
10+
11+
transfer.persist(&block)
12+
end
13+
14+
def initialize(message:)
15+
@message = message
16+
@files = []
17+
@unique_file_names = Set.new
18+
end
19+
20+
# Add files (if still needed)
21+
def persist
22+
yield(self) if block_given?
23+
24+
create_remote_transfer
25+
26+
## files should now be in persisted status
27+
28+
end
29+
30+
# Add one or more files to a transfer, so a transfer can be created over the
31+
# WeTransfer public API
32+
#
33+
# @param name [String] (nil) the name of the file
34+
#
35+
# @return [WeTransfer::Client]
36+
def add_file(name: nil, size: nil, io: nil)
37+
file = WeTransferFile.new(name: name, size: size, io: io)
38+
raise DuplicateFileNameError unless @unique_file_names.add?(file.name.downcase)
39+
40+
@files << file
41+
self
42+
end
43+
44+
private
45+
46+
def as_json_request_params
47+
{
48+
message: @message,
49+
files: @files.map(&:as_json_request_params),
50+
}
51+
end
52+
53+
def create_remote_transfer
54+
raise NoFilesAddedError if @unique_file_names.empty?
55+
56+
response = request_as.post(
57+
'/v2/transfers',
58+
as_json_request_params.to_json,
59+
{}
60+
)
61+
ensure_ok_status!(response)
62+
63+
@remote_transfer = RemoteTransfer.new(JSON.parse(response.body, symbolize_names: true))
64+
end
65+
end
66+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module WeTransfer
2+
class WeTransferFile
3+
4+
attr_reader :name
5+
6+
def initialize(name: nil, size: nil, io: nil)
7+
@io = io.is_a?(MiniIO) ? io : MiniIO.new(io)
8+
@name = name || @io.name
9+
@size = size || @io.size
10+
11+
raise ArgumentError, "Need a file name and a size, or io should provide it" unless @name && @size
12+
end
13+
14+
def as_json_request_params
15+
{
16+
name: @name,
17+
size: @size,
18+
}
19+
end
20+
21+
# def persist()
22+
end
23+
end

lib/we_transfer_client.rb

Lines changed: 53 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
require 'faraday'
24
require 'logger'
35
require 'json'
@@ -16,93 +18,71 @@
1618
require_relative 'we_transfer_client/transfers'
1719
require_relative 'we_transfer_client/boards'
1820

21+
%w[communication_helper transfer mini_io we_transfer_file ].each do |file|
22+
require_relative "we_transfer/#{file}"
23+
end
24+
1925
module WeTransfer
2026
class Client
21-
include WeTransfer::Client::Transfers
22-
include WeTransfer::Client::Boards
27+
include CommunicationHelper
2328

24-
class Error < StandardError
25-
end
29+
class Error < StandardError; end
30+
NullLogger = Logger.new(nil)
2631

27-
NULL_LOGGER = Logger.new(nil)
32+
API_URL_BASE = 'https://dev.wetransfer.com'
2833

29-
def initialize(api_key:, logger: NULL_LOGGER)
30-
@api_url_base = 'https://dev.wetransfer.com'
31-
@api_key = api_key.to_str
34+
# include WeTransfer::Client::Transfers
35+
# include WeTransfer::Client::Boards
36+
37+
## initialize a WeTransfer::Client
38+
#
39+
# @param api_key [String] The API key you want to authenticate with
40+
# @param logger [Logger] (NullLogger) your custom logger
41+
#
42+
# @return [WeTransfer::Client]
43+
def initialize(api_key:, logger: NullLogger)
44+
CommunicationHelper.api_key = api_key
3245
@bearer_token = nil
3346
@logger = logger
47+
CommunicationHelper.logger = logger
3448
end
3549

36-
def upload_file(object:, file:, io:)
37-
put_io_in_parts(object: object, file: file, io: io)
38-
end
39-
40-
def complete_file!(object:, file:)
41-
object.prepare_file_completion(client: self, file: file)
42-
end
50+
def create_transfer(**args, &block)
51+
transfer = WeTransfer::Transfer.new(args, &block)
52+
@transfer = transfer
4353

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

50-
def put_io_in_parts(object:, file:, io:)
51-
(1..file.multipart.part_numbers).each do |part_n_one_based|
52-
upload_url, chunk_size = object.prepare_file_upload(client: self, file: file, part_number: part_n_one_based)
53-
part_io = StringIO.new(io.read(chunk_size))
54-
part_io.rewind
55-
response = faraday.put(
56-
upload_url,
57-
part_io,
58-
'Content-Type' => 'binary/octet-stream',
59-
'Content-Length' => part_io.size.to_s
60-
)
61-
ensure_ok_status!(response)
62-
end
63-
{success: true, message: 'File Uploaded'}
64-
end
58+
# def upload_file(object:, file:, io:)
59+
# put_io_in_parts(object: object, file: file, io: io)
60+
# end
6561

66-
def faraday
67-
Faraday.new(@api_url_base) do |c|
68-
c.response :logger, @logger
69-
c.adapter Faraday.default_adapter
70-
c.headers = { 'User-Agent' => "WetransferRubySdk/#{WeTransfer::VERSION} Ruby #{RUBY_VERSION}"}
71-
end
72-
end
62+
# def complete_file!(object:, file:)
63+
# object.prepare_file_completion(client: self, file: file)
64+
# end
7365

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

84-
def auth_headers
85-
raise 'No bearer token retrieved yet' unless @bearer_token
86-
{
87-
'X-API-Key' => @api_key,
88-
'Authorization' => ('Bearer %s' % @bearer_token),
89-
}
90-
end
91-
92-
def ensure_ok_status!(response)
93-
case response.status
94-
when 200..299
95-
true
96-
when 400..499
97-
@logger.error response
98-
raise Error, "Response had a #{response.status} code, the server will not accept this request even if retried"
99-
when 500..504
100-
@logger.error response
101-
raise Error, "Response had a #{response.status} code, we could retry"
102-
else
103-
@logger.error response
104-
raise Error, "Response had a #{response.status} code, no idea what to do with that"
105-
end
106-
end
72+
# def put_io_in_parts(object:, file:, io:)
73+
# (1..file.multipart.part_numbers).each do |part_n_one_based|
74+
# upload_url, chunk_size = object.prepare_file_upload(client: self, file: file, part_number: part_n_one_based)
75+
# part_io = StringIO.new(io.read(chunk_size))
76+
# part_io.rewind
77+
# response = request_as.put(
78+
# upload_url,
79+
# part_io,
80+
# 'Content-Type' => 'binary/octet-stream',
81+
# 'Content-Length' => part_io.size.to_s
82+
# )
83+
# ensure_ok_status!(response)
84+
# end
85+
# {success: true, message: 'File Uploaded'}
86+
# end
10787
end
10888
end

lib/we_transfer_client/boards.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ def create_feature_board(name:, description:, future_board_class: FutureBoard, b
4040

4141
def create_remote_board(board:, remote_board_class: RemoteBoard)
4242
authorize_if_no_bearer_token!
43-
response = faraday.post(
43+
response = request_as.post(
4444
'/v2/boards',
45-
JSON.pretty_generate(board.to_initial_request_params),
45+
JSON.generate(board.to_initial_request_params),
4646
auth_headers.merge('Content-Type' => 'application/json')
4747
)
4848
ensure_ok_status!(response)
@@ -61,7 +61,7 @@ def add_items_to_remote_board(items:, remote_board:)
6161

6262
def request_board(board:, remote_board_class: RemoteBoard)
6363
authorize_if_no_bearer_token!
64-
response = faraday.get(
64+
response = request_as.get(
6565
"/v2/boards/#{board.id}",
6666
{},
6767
auth_headers.merge('Content-Type' => 'application/json')

0 commit comments

Comments
 (0)