wetransfer 0.4.4 → 0.9.0.beta
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.
- checksums.yaml +5 -5
- data/.rspec +1 -2
- data/.rubocop.yml +1 -1
- data/.travis.yml +3 -2
- data/Guardfile +11 -0
- data/README.md +81 -38
- data/V2_README.md +53 -0
- data/examples/create_collection.rb +13 -0
- data/examples/create_transfer.rb +1 -1
- data/lib/we_transfer_client.rb +86 -120
- data/lib/we_transfer_client/board_builder.rb +33 -0
- data/lib/we_transfer_client/boards.rb +74 -0
- data/lib/we_transfer_client/future_board.rb +32 -0
- data/lib/we_transfer_client/future_file.rb +28 -0
- data/lib/we_transfer_client/future_link.rb +28 -0
- data/lib/we_transfer_client/future_transfer.rb +11 -5
- data/lib/we_transfer_client/remote_board.rb +47 -0
- data/lib/we_transfer_client/remote_file.rb +55 -0
- data/lib/we_transfer_client/remote_link.rb +9 -0
- data/lib/we_transfer_client/remote_transfer.rb +24 -1
- data/lib/we_transfer_client/transfer_builder.rb +3 -10
- data/lib/we_transfer_client/transfers.rb +73 -0
- data/lib/we_transfer_client/version.rb +2 -2
- data/spec/board_integration_spec.rb +68 -0
- data/spec/features/add_items_to_board_spec.rb +55 -0
- data/spec/features/create_board_spec.rb +46 -0
- data/spec/features/get_board_spec.rb +34 -0
- data/spec/features/transfer_spec.rb +41 -0
- data/spec/fixtures/Japan-01.jpg +0 -0
- data/spec/fixtures/Japan-02.jpg +0 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/transfer_integration_spec.rb +54 -0
- data/spec/we_transfer_client/board_builder_spec.rb +66 -0
- data/spec/we_transfer_client/boards_spec.rb +56 -0
- data/spec/we_transfer_client/future_board_spec.rb +98 -0
- data/spec/we_transfer_client/future_file_spec.rb +48 -0
- data/spec/we_transfer_client/future_link_spec.rb +44 -0
- data/spec/we_transfer_client/future_transfer_spec.rb +30 -13
- data/spec/we_transfer_client/remote_board_spec.rb +92 -0
- data/spec/we_transfer_client/remote_file_spec.rb +91 -0
- data/spec/we_transfer_client/remote_link_spec.rb +33 -0
- data/spec/we_transfer_client/remote_transfer_spec.rb +101 -0
- data/spec/we_transfer_client/transfer_builder_spec.rb +48 -51
- data/spec/we_transfer_client/transfers_spec.rb +45 -0
- data/spec/we_transfer_client_spec.rb +51 -3
- data/wetransfer.gemspec +11 -5
- metadata +67 -27
- data/lib/we_transfer_client/future_file_item.rb +0 -16
- data/lib/we_transfer_client/future_web_item.rb +0 -18
- data/lib/we_transfer_client/remote_item.rb +0 -2
- data/spec/integration_spec.rb +0 -160
- data/spec/we_transfer_client/future_file_item_spec.rb +0 -39
- data/spec/we_transfer_client/future_web_item_spec.rb +0 -39
@@ -0,0 +1,33 @@
|
|
1
|
+
class BoardBuilder
|
2
|
+
attr_reader :items
|
3
|
+
class TransferIOError < StandardError; end
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@items = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_file(name:, io:)
|
10
|
+
ensure_io_compliant!(io)
|
11
|
+
@items << FutureFile.new(name: name, io: io)
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_file_at(path:)
|
15
|
+
add_file(name: File.basename(path), io: File.open(path, 'rb'))
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_web_url(url:, title: url)
|
19
|
+
@items << FutureLink.new(url: url, title: title)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def ensure_io_compliant!(io)
|
25
|
+
io.seek(0)
|
26
|
+
io.read(1) # Will cause things like Errno::EACCESS to happen early, before the upload begins
|
27
|
+
io.seek(0) # Also rewinds the IO for later uploading action
|
28
|
+
size = io.size # Will cause a NoMethodError
|
29
|
+
raise TransferIOError, "#{File.basename(io)}, given to add_file has a size of 0" if size <= 0
|
30
|
+
rescue NoMethodError
|
31
|
+
raise TransferIOError, "#{File.basename(io)}, given to add_file must respond to seek(), read() and size(), but #{io.inspect} did not"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module WeTransfer
|
2
|
+
class Client
|
3
|
+
module Boards
|
4
|
+
def create_board_and_upload_items(name:, description:, &block)
|
5
|
+
future_board = create_feature_board(name: name, description: description, &block)
|
6
|
+
remote_board = create_remote_board(board: future_board)
|
7
|
+
remote_board.files.each do |file|
|
8
|
+
check_for_file_duplicates(future_board.files, file)
|
9
|
+
local_file = future_board.files.select { |x| x.name == file.name }.first
|
10
|
+
upload_file(object: remote_board, file: file, io: local_file.io)
|
11
|
+
complete_file!(object: remote_board, file: file)
|
12
|
+
end
|
13
|
+
remote_board
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_board(board:)
|
17
|
+
request_board(board: board)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_board(name:, description:, &block)
|
23
|
+
future_board = create_feature_board(name: name, description: description, &block)
|
24
|
+
create_remote_board(board: future_board)
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_items(board:, board_builder_class: BoardBuilder)
|
28
|
+
builder = board_builder_class.new
|
29
|
+
yield(builder)
|
30
|
+
add_items_to_remote_board(items: builder.items, remote_board: board)
|
31
|
+
rescue LocalJumpError
|
32
|
+
raise ArgumentError, 'No items where added to the board'
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_feature_board(name:, description:, future_board_class: FutureBoard, board_builder_class: BoardBuilder)
|
36
|
+
builder = board_builder_class.new
|
37
|
+
yield(builder) if block_given?
|
38
|
+
future_board_class.new(name: name, description: description, items: builder.items)
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_remote_board(board:, remote_board_class: RemoteBoard)
|
42
|
+
authorize_if_no_bearer_token!
|
43
|
+
response = faraday.post(
|
44
|
+
'/v2/boards',
|
45
|
+
JSON.pretty_generate(board.to_initial_request_params),
|
46
|
+
auth_headers.merge('Content-Type' => 'application/json')
|
47
|
+
)
|
48
|
+
ensure_ok_status!(response)
|
49
|
+
remote_board = remote_board_class.new(JSON.parse(response.body, symbolize_names: true))
|
50
|
+
board.items.any? ? add_items_to_remote_board(items: board.items, remote_board: remote_board) : remote_board
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_items_to_remote_board(items:, remote_board:)
|
54
|
+
items.group_by(&:class).each do |_, grouped_items|
|
55
|
+
grouped_items.each do |item|
|
56
|
+
item.add_to_board(client: self, remote_board: remote_board)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
remote_board
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_board(board:, remote_board_class: RemoteBoard)
|
63
|
+
authorize_if_no_bearer_token!
|
64
|
+
response = faraday.get(
|
65
|
+
"/v2/boards/#{board.id}",
|
66
|
+
{},
|
67
|
+
auth_headers.merge('Content-Type' => 'application/json')
|
68
|
+
)
|
69
|
+
ensure_ok_status!(response)
|
70
|
+
remote_board_class.new(JSON.parse(response.body, symbolize_names: true))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class FutureBoard
|
2
|
+
attr_reader :name, :description, :items
|
3
|
+
|
4
|
+
def initialize(name:, description: nil, items: [])
|
5
|
+
@name = name
|
6
|
+
@description = description
|
7
|
+
@items = items
|
8
|
+
end
|
9
|
+
|
10
|
+
def files
|
11
|
+
@items.select { |item| item.class == FutureFile }
|
12
|
+
end
|
13
|
+
|
14
|
+
def links
|
15
|
+
@items.select { |item| item.class == FutureLink }
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_initial_request_params
|
19
|
+
{
|
20
|
+
name: name,
|
21
|
+
description: description,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_request_params
|
26
|
+
{
|
27
|
+
name: name,
|
28
|
+
description: description,
|
29
|
+
items: items.map(&:to_request_params),
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class FutureFile
|
2
|
+
attr_reader :name, :io
|
3
|
+
|
4
|
+
def initialize(name:, io:)
|
5
|
+
@name = name
|
6
|
+
@io = io
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_request_params
|
10
|
+
{
|
11
|
+
name: @name,
|
12
|
+
size: @io.size,
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_to_board(client:, remote_board:)
|
17
|
+
client.authorize_if_no_bearer_token!
|
18
|
+
response = client.faraday.post(
|
19
|
+
"/v2/boards/#{remote_board.id}/files",
|
20
|
+
# this needs to be a array with hashes => [{name, filesize}]
|
21
|
+
JSON.pretty_generate([to_request_params]),
|
22
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
23
|
+
)
|
24
|
+
client.ensure_ok_status!(response)
|
25
|
+
file_item = JSON.parse(response.body, symbolize_names: true).first
|
26
|
+
remote_board.items << RemoteFile.new(file_item)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class FutureLink
|
2
|
+
attr_reader :url, :title
|
3
|
+
|
4
|
+
def initialize(url:, title: url)
|
5
|
+
@url = url
|
6
|
+
@title = title
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_request_params
|
10
|
+
{
|
11
|
+
url: url,
|
12
|
+
title: title,
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_to_board(client:, remote_board:)
|
17
|
+
client.authorize_if_no_bearer_token!
|
18
|
+
response = client.faraday.post(
|
19
|
+
"/v2/boards/#{remote_board.id}/links",
|
20
|
+
# this needs to be a array with hashes => [{name, filesize}]
|
21
|
+
JSON.pretty_generate([to_request_params]),
|
22
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
23
|
+
)
|
24
|
+
client.ensure_ok_status!(response)
|
25
|
+
file_item = JSON.parse(response.body, symbolize_names: true).first
|
26
|
+
remote_board.items << RemoteLink.new(file_item)
|
27
|
+
end
|
28
|
+
end
|
@@ -1,9 +1,15 @@
|
|
1
|
-
class FutureTransfer
|
2
|
-
|
1
|
+
class FutureTransfer
|
2
|
+
attr_accessor :message, :files
|
3
|
+
|
4
|
+
def initialize(message:, files: [])
|
5
|
+
@message = message
|
6
|
+
@files = files
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_request_params
|
3
10
|
{
|
4
|
-
|
5
|
-
|
6
|
-
items: items.map(&:to_item_request_params),
|
11
|
+
message: message,
|
12
|
+
files: files.map(&:to_request_params),
|
7
13
|
}
|
8
14
|
end
|
9
15
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class RemoteBoard
|
2
|
+
ItemTypeError = Class.new(NameError)
|
3
|
+
|
4
|
+
attr_reader :id, :items, :url, :state
|
5
|
+
|
6
|
+
CHUNK_SIZE = 6 * 1024 * 1024
|
7
|
+
|
8
|
+
def initialize(id:, state:, url:, name:, description: '', items: [])
|
9
|
+
@id = id
|
10
|
+
@state = state
|
11
|
+
@url = url
|
12
|
+
@name = name
|
13
|
+
@description = description
|
14
|
+
@items = to_instances(items: items)
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_file_upload(client:, file:, part_number:)
|
18
|
+
url = file.request_board_upload_url(client: client, board_id: @id, part_number: part_number)
|
19
|
+
[url, CHUNK_SIZE]
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_file_completion(client:, file:)
|
23
|
+
file.complete_board_file(client: client, board_id: @id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def files
|
27
|
+
@items.select { |item| item.class == RemoteFile }
|
28
|
+
end
|
29
|
+
|
30
|
+
def links
|
31
|
+
@items.select { |item| item.class == RemoteLink }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def to_instances(items:)
|
37
|
+
items.map do |item|
|
38
|
+
begin
|
39
|
+
remote_class = "Remote#{item[:type].capitalize}"
|
40
|
+
Module.const_get(remote_class)
|
41
|
+
.new(item)
|
42
|
+
rescue NameError
|
43
|
+
raise ItemTypeError, "Cannot instantiate item with type '#{item[:type]}' and id '#{item[:id]}'"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class RemoteFile
|
2
|
+
attr_reader :multipart, :name, :id, :url, :type
|
3
|
+
|
4
|
+
def initialize(id:, name:, size:, url: nil, type: 'file', multipart:)
|
5
|
+
@id = id
|
6
|
+
@name = name
|
7
|
+
@size = size
|
8
|
+
@url = url
|
9
|
+
@type = type
|
10
|
+
@size = size
|
11
|
+
multi = Struct.new(*multipart.keys)
|
12
|
+
@multipart = multi.new(*multipart.values)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_transfer_upload_url(client:, transfer_id:, part_number:)
|
16
|
+
response = client.faraday.get(
|
17
|
+
"/v2/transfers/#{transfer_id}/files/#{@id}/upload-url/#{part_number}",
|
18
|
+
{},
|
19
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
20
|
+
)
|
21
|
+
client.ensure_ok_status!(response)
|
22
|
+
JSON.parse(response.body, symbolize_names: true).fetch(:url)
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_board_upload_url(client:, board_id:, part_number:)
|
26
|
+
response = client.faraday.get(
|
27
|
+
"/v2/boards/#{board_id}/files/#{@id}/upload-url/#{part_number}/#{@multipart.id}",
|
28
|
+
{},
|
29
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
30
|
+
)
|
31
|
+
client.ensure_ok_status!(response)
|
32
|
+
JSON.parse(response.body, symbolize_names: true).fetch(:url)
|
33
|
+
end
|
34
|
+
|
35
|
+
def complete_transfer_file(client:, transfer_id:)
|
36
|
+
body = {part_numbers: @multipart.part_numbers}
|
37
|
+
response = client.faraday.put(
|
38
|
+
"/v2/transfers/#{transfer_id}/files/#{@id}/upload-complete",
|
39
|
+
JSON.pretty_generate(body),
|
40
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
41
|
+
)
|
42
|
+
client.ensure_ok_status!(response)
|
43
|
+
JSON.parse(response.body, symbolize_names: true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def complete_board_file(client:, board_id:)
|
47
|
+
response = client.faraday.put(
|
48
|
+
"/v2/boards/#{board_id}/files/#{@id}/upload-complete",
|
49
|
+
'{}',
|
50
|
+
client.auth_headers.merge('Content-Type' => 'application/json')
|
51
|
+
)
|
52
|
+
client.ensure_ok_status!(response)
|
53
|
+
JSON.parse(response.body, symbolize_names: true)
|
54
|
+
end
|
55
|
+
end
|
@@ -1,2 +1,25 @@
|
|
1
|
-
class RemoteTransfer
|
1
|
+
class RemoteTransfer
|
2
|
+
attr_reader :files, :url, :state, :id
|
3
|
+
|
4
|
+
def initialize(id:, state:, url:, message:, files: [])
|
5
|
+
@id = id
|
6
|
+
@state = state
|
7
|
+
@message = message
|
8
|
+
@url = url
|
9
|
+
@files = files_to_class(files)
|
10
|
+
end
|
11
|
+
|
12
|
+
def prepare_file_upload(client:, file:, part_number:)
|
13
|
+
url = file.request_transfer_upload_url(client: client, transfer_id: @id, part_number: part_number)
|
14
|
+
chunk_size = file.multipart.chunk_size
|
15
|
+
[url, chunk_size]
|
16
|
+
end
|
17
|
+
|
18
|
+
def prepare_file_completion(client:, file:)
|
19
|
+
file.complete_transfer_file(client: client, transfer_id: @id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def files_to_class(files)
|
23
|
+
files.map { |x| RemoteFile.new(x) }
|
24
|
+
end
|
2
25
|
end
|
@@ -1,27 +1,20 @@
|
|
1
1
|
class TransferBuilder
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :files
|
3
3
|
class TransferIOError < StandardError; end
|
4
4
|
|
5
5
|
def initialize
|
6
|
-
@
|
6
|
+
@files = []
|
7
7
|
end
|
8
8
|
|
9
9
|
def add_file(name:, io:)
|
10
10
|
ensure_io_compliant!(io)
|
11
|
-
@
|
12
|
-
true
|
11
|
+
@files << FutureFile.new(name: name, io: io)
|
13
12
|
end
|
14
13
|
|
15
14
|
def add_file_at(path:)
|
16
15
|
add_file(name: File.basename(path), io: File.open(path, 'rb'))
|
17
16
|
end
|
18
17
|
|
19
|
-
def add_web_url(url:, title: nil)
|
20
|
-
title ||= url
|
21
|
-
@items << FutureWebItem.new(url: url, title: title)
|
22
|
-
true
|
23
|
-
end
|
24
|
-
|
25
18
|
def ensure_io_compliant!(io)
|
26
19
|
io.seek(0)
|
27
20
|
io.read(1) # Will cause things like Errno::EACCESS to happen early, before the upload begins
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module WeTransfer
|
2
|
+
class Client
|
3
|
+
module Transfers
|
4
|
+
def create_transfer_and_upload_files(message:, &block)
|
5
|
+
future_transfer = create_future_transfer(message: message, &block)
|
6
|
+
remote_transfer = create_remote_transfer(future_transfer)
|
7
|
+
remote_transfer.files.each do |file|
|
8
|
+
check_for_file_duplicates(future_transfer.files, file)
|
9
|
+
local_file = future_transfer.files.select { |x| x.name == file.name }.first
|
10
|
+
upload_file(object: remote_transfer, file: file, io: local_file.io)
|
11
|
+
complete_file!(object: remote_transfer, file: file)
|
12
|
+
end
|
13
|
+
complete_transfer(transfer: remote_transfer)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_transfer(transfer_id:)
|
17
|
+
request_transfer(transfer_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_transfer(message:, &block)
|
23
|
+
transfer = create_future_transfer(message: message, &block)
|
24
|
+
create_remote_transfer(transfer)
|
25
|
+
end
|
26
|
+
|
27
|
+
def complete_transfer(transfer:)
|
28
|
+
complete_transfer_call(transfer)
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_future_transfer(message:, future_transfer_class: FutureTransfer, transfer_builder_class: TransferBuilder)
|
32
|
+
builder = transfer_builder_class.new
|
33
|
+
yield(builder)
|
34
|
+
future_transfer_class.new(message: message, files: builder.files)
|
35
|
+
rescue LocalJumpError
|
36
|
+
raise ArgumentError, 'No files were added to transfer'
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_remote_transfer(xfer)
|
40
|
+
authorize_if_no_bearer_token!
|
41
|
+
response = faraday.post(
|
42
|
+
'/v2/transfers',
|
43
|
+
JSON.pretty_generate(xfer.to_request_params),
|
44
|
+
auth_headers.merge('Content-Type' => 'application/json')
|
45
|
+
)
|
46
|
+
ensure_ok_status!(response)
|
47
|
+
RemoteTransfer.new(JSON.parse(response.body, symbolize_names: true))
|
48
|
+
end
|
49
|
+
|
50
|
+
def complete_transfer_call(object)
|
51
|
+
authorize_if_no_bearer_token!
|
52
|
+
response = faraday.put(
|
53
|
+
"/v2/transfers/#{object.id}/finalize",
|
54
|
+
'',
|
55
|
+
auth_headers.merge('Content-Type' => 'application/json')
|
56
|
+
)
|
57
|
+
ensure_ok_status!(response)
|
58
|
+
RemoteTransfer.new(JSON.parse(response.body, symbolize_names: true))
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_transfer(transfer_id)
|
62
|
+
authorize_if_no_bearer_token!
|
63
|
+
response = faraday.get(
|
64
|
+
"/v2/transfers/#{transfer_id}",
|
65
|
+
{},
|
66
|
+
auth_headers.merge('Content-Type' => 'application/json')
|
67
|
+
)
|
68
|
+
ensure_ok_status!(response)
|
69
|
+
RemoteTransfer.new(JSON.parse(response.body, symbolize_names: true))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|