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.
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