wetransfer 0.4.4 → 0.9.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +1 -2
  3. data/.rubocop.yml +1 -1
  4. data/.travis.yml +3 -2
  5. data/Guardfile +11 -0
  6. data/README.md +81 -38
  7. data/V2_README.md +53 -0
  8. data/examples/create_collection.rb +13 -0
  9. data/examples/create_transfer.rb +1 -1
  10. data/lib/we_transfer_client.rb +86 -120
  11. data/lib/we_transfer_client/board_builder.rb +33 -0
  12. data/lib/we_transfer_client/boards.rb +74 -0
  13. data/lib/we_transfer_client/future_board.rb +32 -0
  14. data/lib/we_transfer_client/future_file.rb +28 -0
  15. data/lib/we_transfer_client/future_link.rb +28 -0
  16. data/lib/we_transfer_client/future_transfer.rb +11 -5
  17. data/lib/we_transfer_client/remote_board.rb +47 -0
  18. data/lib/we_transfer_client/remote_file.rb +55 -0
  19. data/lib/we_transfer_client/remote_link.rb +9 -0
  20. data/lib/we_transfer_client/remote_transfer.rb +24 -1
  21. data/lib/we_transfer_client/transfer_builder.rb +3 -10
  22. data/lib/we_transfer_client/transfers.rb +73 -0
  23. data/lib/we_transfer_client/version.rb +2 -2
  24. data/spec/board_integration_spec.rb +68 -0
  25. data/spec/features/add_items_to_board_spec.rb +55 -0
  26. data/spec/features/create_board_spec.rb +46 -0
  27. data/spec/features/get_board_spec.rb +34 -0
  28. data/spec/features/transfer_spec.rb +41 -0
  29. data/spec/fixtures/Japan-01.jpg +0 -0
  30. data/spec/fixtures/Japan-02.jpg +0 -0
  31. data/spec/spec_helper.rb +20 -0
  32. data/spec/transfer_integration_spec.rb +54 -0
  33. data/spec/we_transfer_client/board_builder_spec.rb +66 -0
  34. data/spec/we_transfer_client/boards_spec.rb +56 -0
  35. data/spec/we_transfer_client/future_board_spec.rb +98 -0
  36. data/spec/we_transfer_client/future_file_spec.rb +48 -0
  37. data/spec/we_transfer_client/future_link_spec.rb +44 -0
  38. data/spec/we_transfer_client/future_transfer_spec.rb +30 -13
  39. data/spec/we_transfer_client/remote_board_spec.rb +92 -0
  40. data/spec/we_transfer_client/remote_file_spec.rb +91 -0
  41. data/spec/we_transfer_client/remote_link_spec.rb +33 -0
  42. data/spec/we_transfer_client/remote_transfer_spec.rb +101 -0
  43. data/spec/we_transfer_client/transfer_builder_spec.rb +48 -51
  44. data/spec/we_transfer_client/transfers_spec.rb +45 -0
  45. data/spec/we_transfer_client_spec.rb +51 -3
  46. data/wetransfer.gemspec +11 -5
  47. metadata +67 -27
  48. data/lib/we_transfer_client/future_file_item.rb +0 -16
  49. data/lib/we_transfer_client/future_web_item.rb +0 -18
  50. data/lib/we_transfer_client/remote_item.rb +0 -2
  51. data/spec/integration_spec.rb +0 -160
  52. data/spec/we_transfer_client/future_file_item_spec.rb +0 -39
  53. 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 < Ks.strict(:name, :description, :items)
2
- def to_create_transfer_params
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
- name: name,
5
- description: description,
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
@@ -0,0 +1,9 @@
1
+ class RemoteLink
2
+ attr_reader :type
3
+ def initialize(id:, url:, type:, meta:)
4
+ @id = id
5
+ @url = url
6
+ @title = meta.fetch(:title)
7
+ @type = type
8
+ end
9
+ end
@@ -1,2 +1,25 @@
1
- class RemoteTransfer < Ks.strict(:id, :version_identifier, :state, :shortened_url, :name, :description, :size, :items)
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 :items
2
+ attr_reader :files
3
3
  class TransferIOError < StandardError; end
4
4
 
5
5
  def initialize
6
- @items = []
6
+ @files = []
7
7
  end
8
8
 
9
9
  def add_file(name:, io:)
10
10
  ensure_io_compliant!(io)
11
- @items << FutureFileItem.new(name: name, io: io)
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