afmotion 2.6 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZGU4YjcxZDY0YTVlMmM2OTIxNzYyZDhmMDBiOGQwYjhkMzkzNDJkYg==
5
- data.tar.gz: !binary |-
6
- NDFlOWRlZmFlOTkzNTgyMTQyMGUxMzlkYWI1MDcwNDQxYjA4NWMxNg==
2
+ SHA256:
3
+ metadata.gz: b0972df3e642f569351b4c4f0897ca9969188a776751add18a3a58e0b9df0fbb
4
+ data.tar.gz: 7d6968c3d2cadd9b9b9a8185e912307bf43c2f7ed1fd26a49b0437eececa1bf3
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MWRhOGVkYzNiYTE3MDRlNmExYzQ5YTQ3N2MyYjU4ZmYxNTRlMDA5OTNiMjAx
10
- ZDI2MjMwNTVjNzA5MWM4Y2E5NmE3NTBhNWQyNWIwNTA1NGQ5NDBlNmYxNDE1
11
- Njk2MzJhMjk3YWNkMjY4OWUwYzgwYWU0Yjc3NmIzMTk3MmU4YTQ=
12
- data.tar.gz: !binary |-
13
- ZWFlYjBiNjhiZDU3NmFhNTE4ZWNiNzU2OTk3YzY5N2M3MWY2NGUxZmVkMzQ3
14
- NTZiZWU2Yzg1NzIwNTRjNjlkYzdmMDJmYTIyNTk4ZGY0YTI3ZmVlODYxMzFm
15
- NWFmZmEzM2IwN2UwM2Y5M2M5YmQwNzcxM2UwZWZhMTM1ZTRkMGU=
6
+ metadata.gz: fff5abc13b536be3b7318989271362532ebecfb9a50c7bbbfc52379eed79c575c0574f8794a1dfa13b1343fb7b0afec3d586a2481ac7a9787c3d387c05c523c1
7
+ data.tar.gz: ea3ae7b135bd574f31b0dd16ba990cc672c83823f56d6a1defe1fecdd0cbece06c066460347da7027ef7ad0b193b5ec191aa58f8582c18946dc6cb34e5740583
data/.gitignore CHANGED
@@ -14,4 +14,5 @@ nbproject
14
14
  *~
15
15
  *.sw[po]
16
16
  .eprj
17
- pkg/*
17
+ pkg/*
18
+ Gemfile.lock
@@ -15,8 +15,8 @@ Gem::Specification.new do |s|
15
15
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
16
  s.require_paths = ["lib"]
17
17
 
18
- s.add_dependency "motion-cocoapods", ">= 1.4.1"
18
+ s.add_dependency "motion-cocoapods", ">= 1.9.1"
19
19
  s.add_dependency "motion-require", ">= 0.1"
20
20
  s.add_development_dependency 'rake'
21
- s.add_development_dependency 'webstub', "~> 1.1"
21
+ s.add_development_dependency 'webstub', "~> 1.1.6"
22
22
  end
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # AFMotion
2
2
 
3
- [![Build Status](https://travis-ci.org/clayallsopp/afmotion.png?branch=master)](https://travis-ci.org/clayallsopp/afmotion)
3
+ [![Build Status](https://travis-ci.org/clayallsopp/afmotion.png?branch=master)](https://travis-ci.org/clayallsopp/afmotion) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion.svg?size=small)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion?ref=badge_small)
4
4
 
5
- AFMotion is a thin RubyMotion wrapper for [AFNetworking](https://github.com/AFNetworking/AFNetworking), the absolute best networking library on iOS.
5
+ AFMotion is a thin RubyMotion wrapper for [AFNetworking](https://github.com/AFNetworking/AFNetworking), the absolute best networking library on iOS and OS X.
6
6
 
7
7
  ## Usage
8
8
 
@@ -33,32 +33,45 @@ end
33
33
  end
34
34
  ```
35
35
 
36
- You can either use `AFMotion::Client` or `AFMotion::SessionClient` to group similar requests. They have identical APIs, except for their creation and that their request `result` objects contain either `result.operation` (for `::Client`) or `result.task` (for `::SessionClient`).
36
+ #### Migration from AFMotion 2.x
37
37
 
38
- #### AFMotion::Client
38
+ _Breaking Change_
39
+ Parameters must now be specified with the `params:` keyword arg.
39
40
 
40
- If you're interacting with a web service, you can use [`AFHTTPRequestOperationManager`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPRequestOperationManager.html) with this nice wrapper:
41
+ AFMotion 2.x
41
42
 
42
43
  ```ruby
43
- # DSL Mapping to properties of AFHTTPRequestOperationManager
44
+ AFMotion::HTTP.get("http://google.com", q: "rubymotion") do |result|
45
+ # sends request to http://google.com?q=rubymotion
46
+ end
47
+ ```
44
48
 
45
- @client = AFMotion::Client.build("https://alpha-api.app.net/") do
46
- header "Accept", "application/json"
49
+ AFMotion 3.x
47
50
 
48
- response_serializer :json
51
+ ```ruby
52
+ AFMotion::HTTP.get("http://google.com", params: { q: "rubymotion" }) do |result|
53
+ # sends request to http://google.com?q=rubymotion
49
54
  end
50
55
  ```
51
56
 
52
- #### AFMotion::SessionClient
53
-
54
- If you're using iOS7, you can use [`AFHTTPSessionManager`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPSessionManager.html):
57
+ This allows you to also pass in a progress_block or additional headers on the fly:
55
58
 
56
59
  ```ruby
57
- # DSL Mapping to properties of AFHTTPSessionManager
60
+ AFMotion::HTTP.get("http://url.com/large_file.mov", params: { quality: "high" }, progress_block: proc { |progress| update_progress(progress) }, headers: {}) do |result|
61
+ # sends request to http://google.com?q=rubymotion
62
+ end
63
+ ```
58
64
 
59
- @client = AFMotion::SessionClient.build("https://alpha-api.app.net/") do
60
- session_configuration :default
65
+ For grouping similar requests (AFHTTPSession), use `AFMotion::Client` (now exactly the same as `AFMotion::SessionClient`)
61
66
 
67
+ #### AFMotion::Client
68
+
69
+ If you're interacting with a web service, you can use [`AFHTTPRequestOperationManager`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPRequestOperationManager.html) with this nice wrapper:
70
+
71
+ ```ruby
72
+ # DSL Mapping to properties of AFHTTPRequestOperationManager
73
+
74
+ @client = AFMotion::Client.build("https://alpha-api.app.net/") do
62
75
  header "Accept", "application/json"
63
76
 
64
77
  response_serializer :json
@@ -103,8 +116,7 @@ Each AFMotion wrapper callback yields an `AFMotion::HTTPResult` object. This obj
103
116
 
104
117
  ```ruby
105
118
  AFMotion::some_function do |result|
106
- # result.operation is the AFURLConnectionOperation instance
107
- p result.operation.inspect
119
+ p result.task.inspect
108
120
  p result.status_code
109
121
 
110
122
  if result.success?
@@ -135,7 +147,7 @@ end
135
147
  Example:
136
148
 
137
149
  ```ruby
138
- AFMotion::HTTP.get("http://google.com", q: "rubymotion") do |result|
150
+ AFMotion::HTTP.get("http://google.com", params: { q: "rubymotion" }) do |result|
139
151
  # sends request to http://google.com?q=rubymotion
140
152
  end
141
153
  ```
@@ -148,7 +160,7 @@ end
148
160
 
149
161
  ### HTTP Client
150
162
 
151
- If you're constantly accesing a web service, it's a good idea to use an `AFHTTPRequestOperationManager`. Things lets you add a common base URL and request headers to all the requests issued through it, like so:
163
+ If you're constantly accesing a web service, it's a good idea to use an `AFHTTPSessionManager`. Things lets you add a common base URL and request headers to all the requests issued through it, like so:
152
164
 
153
165
  ```ruby
154
166
  client = AFMotion::Client.build("https://alpha-api.app.net/") do
@@ -163,42 +175,23 @@ client.get("stream/0/posts/stream/global") do |result|
163
175
  end
164
176
  ```
165
177
 
166
- If you're using iOS7, you can use [`AFHTTPSessionManager`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Classes/AFHTTPSessionManager.html):
167
-
168
- ```ruby
169
- # DSL Mapping to properties of AFHTTPSessionManager
170
-
171
- client = AFMotion::SessionClient.build("https://alpha-api.app.net/") do
172
- session_configuration :default
173
-
174
- header "Accept", "application/json"
175
-
176
- response_serializer :json
177
- end
178
-
179
- client.get("stream/0/posts/stream/global") do |result|
180
- # result.task exists
181
- ...
182
- end
183
- ```
184
-
185
178
  If you're constantly used one web service, you can use the `AFMotion::Client.shared` variable have a common reference. It can be set like a normal variable or created with `AFMotion::Client.build_shared`.
186
179
 
187
180
  `AFHTTPRequestOperationManager` & `AFHTTPSessionManager` support methods of the form `Client#get/post/put/patch/delete(url, request_parameters)`. The `request_parameters` is a hash containing your parameters to attach as the request body or URL parameters, depending on request type. For example:
188
181
 
189
182
  ```ruby
190
- client.get("users", id: 1) do |result|
183
+ client.get("users", params: { id: 1 }) do |result|
191
184
  ...
192
185
  end
193
186
 
194
- client.post("users", name: "@clayallsopp", library: "AFMotion") do |result|
187
+ client.post("users", params: { name: "@clayallsopp", library: "AFMotion" }) do |result|
195
188
  ...
196
189
  end
197
190
  ```
198
191
 
199
192
  #### Multipart Requests
200
193
 
201
- `AFHTTPRequestOperationManager` & `AFHTTPSessionManager` support multipart form requests (i.e. for image uploading) - simply use `multipart_post` and it'll convert your parameters into properly encoded multipart data. For all other types of request data, use the `form_data` object passed to your callback:
194
+ `AFHTTPSessionManager` support multipart form requests (i.e. for image uploading) - simply use `multipart_post` and it'll convert your parameters into properly encoded multipart data. For all other types of request data, use the `form_data` object passed to your callback:
202
195
 
203
196
  ```ruby
204
197
  # an instance of UIImage
@@ -220,17 +213,14 @@ end
220
213
 
221
214
  This is an instance of [`AFMultipartFormData`](http://cocoadocs.org/docsets/AFNetworking/2.0.0/Protocols/AFMultipartFormData.html).
222
215
 
223
- If you want to track upload progress, you can add a third callback argument which returns the upload percentage between 0.0 and 1.0:
216
+ If you want to track upload progress, simply add a progress_block (Taking a single arg: `NSProgress`)
224
217
 
225
218
  ```ruby
226
- client.multipart_post("avatars") do |result, form_data, progress|
219
+ client.multipart_post("avatars", progress_block: proc { |progress| update_progress(progress) }) do |result, form_data|
227
220
  if form_data
228
221
  # Called before request runs
229
222
  # see: https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ
230
223
  form_data.appendPartWithFileData(data, name: "avatar", fileName:"avatar.png", mimeType: "image/png")
231
- elsif progress
232
- # 0.0 < progress < 1.0
233
- my_widget.update_progress(progress)
234
224
  else
235
225
  ...
236
226
  end
@@ -273,3 +263,7 @@ client = AFMotion::SessionClient.build("https://alpha-api.app.net/") do |client|
273
263
  client.header "Accept", @custom_header
274
264
  end
275
265
  ```
266
+
267
+ ## License
268
+
269
+ [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion.svg?size=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fclayallsopp%2Fafmotion?ref=badge_large)
data/Rakefile CHANGED
@@ -1,7 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  $:.unshift("/Library/RubyMotion/lib")
3
+ $:.unshift("~/.rubymotion/rubymotion-templates") # Add this line
4
+ require 'motion/project/template/gem/gem_tasks'
3
5
  require 'motion/project/template/ios'
4
- require "bundler/gem_tasks"
5
6
  require "bundler/setup"
6
7
  Bundler.require :default
7
8
 
@@ -13,5 +14,5 @@ require 'webstub'
13
14
  Motion::Project::App.setup do |app|
14
15
  # Use `rake config' to see complete project settings.
15
16
  app.name = 'AFMotion'
16
- app.deployment_target = "7.1"
17
+ app.deployment_target = "10.0"
17
18
  end
@@ -16,6 +16,6 @@ Motion::Project::App.setup do |app|
16
16
  end
17
17
 
18
18
  app.pods do
19
- pod 'AFNetworking', '~> 2.5.0'
19
+ pod 'AFNetworking', '~> 4.0.0'
20
20
  end
21
21
  end
@@ -1,31 +1,4 @@
1
1
  module AFMotion
2
- # ported from https://github.com/AFNetworking/AFNetworking/blob/master/UIKit%2BAFNetworking/UIProgressView%2BAFNetworking.m
3
- class SessionObserver
4
-
5
- def initialize(task, callback)
6
- @callback = callback
7
- task.addObserver(self, forKeyPath:"state", options:0, context:nil)
8
- task.addObserver(self, forKeyPath:"countOfBytesSent", options:0, context:nil)
9
- end
10
-
11
- def observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
12
- if keyPath == "countOfBytesSent"
13
- # Could be -1, see https://github.com/AFNetworking/AFNetworking/issues/1354
14
- expectation = (object.countOfBytesExpectedToSend > 0) ? object.countOfBytesExpectedToSend.to_f : nil
15
- @callback.call(nil, object.countOfBytesSent.to_f, expectation)
16
- end
17
-
18
- if keyPath == "state" && object.state == NSURLSessionTaskStateCompleted
19
- begin
20
- object.removeObserver(self, forKeyPath: "state")
21
- object.removeObserver(self, forKeyPath: "countOfBytesSent")
22
- @callback = nil
23
- rescue
24
- end
25
- end
26
- end
27
- end
28
-
29
2
  module ClientShared
30
3
  def headers
31
4
  requestSerializer.headers
@@ -39,29 +12,25 @@ module AFMotion
39
12
  requestSerializer.authorization = authorization
40
13
  end
41
14
 
42
- def multipart_post(path, parameters = {}, &callback)
43
- create_multipart_operation(:post, path, parameters, &callback)
15
+ def multipart_post(path, options = {}, &callback)
16
+ create_multipart_task(:post, path, options, &callback)
44
17
  end
45
18
 
46
- def multipart_put(path, parameters = {}, &callback)
47
- create_multipart_operation(:put, path, parameters, &callback)
19
+ def multipart_put(path, options = {}, &callback)
20
+ create_multipart_task(:put, path, options, &callback)
48
21
  end
49
22
 
50
- def create_multipart_operation(http_method, path, parameters = {}, &callback)
51
- inner_callback = Proc.new do |result, form_data, bytes_written_now, total_bytes_written, total_bytes_expect|
23
+ def create_multipart_task(http_method, path, options = {}, &callback)
24
+ parameters = options[:params]
25
+ headers = options.fetch(:headers, {})
26
+ progress = options[:progress_block]
27
+
28
+ inner_callback = Proc.new do |result, form_data|
52
29
  case callback.arity
53
30
  when 1
54
31
  callback.call(result)
55
32
  when 2
56
33
  callback.call(result, form_data)
57
- when 3
58
- progress = nil
59
- if total_bytes_written && total_bytes_expect
60
- progress = total_bytes_written.to_f / total_bytes_expect.to_f
61
- end
62
- callback.call(result, form_data, progress)
63
- when 5
64
- callback.call(result, form_data, bytes_written_now, total_bytes_written, total_bytes_expect)
65
34
  end
66
35
  end
67
36
 
@@ -72,62 +41,77 @@ module AFMotion
72
41
  }
73
42
  end
74
43
 
75
- upload_callback = nil
76
- if callback.arity > 2
77
- upload_callback = lambda { |bytes_written_now, total_bytes_written, total_bytes_expect|
78
- inner_callback.call(nil, nil, bytes_written_now, total_bytes_written, total_bytes_expect)
79
- }
80
- end
81
-
82
44
  http_method = http_method.to_s.upcase
83
45
  if http_method == "POST"
84
- operation_or_task = self.POST(path,
46
+ task = self.POST(path,
85
47
  parameters: parameters,
48
+ headers: headers,
86
49
  constructingBodyWithBlock: multipart_callback,
87
- success: AFMotion::Operation.success_block_for_http_method(:post, inner_callback),
88
- failure: AFMotion::Operation.failure_block(inner_callback))
50
+ progress: progress,
51
+ success: success_block_for_http_method(:post, inner_callback),
52
+ failure: failure_block(inner_callback))
89
53
  else
90
- operation_or_task = self.PUT(path,
54
+ task = self.PUT(path,
91
55
  parameters: parameters,
56
+ headers: headers,
92
57
  constructingBodyWithBlock: multipart_callback,
93
- success: AFMotion::Operation.success_block_for_http_method(:post, inner_callback),
94
- failure: AFMotion::Operation.failure_block(inner_callback))
58
+ progress: progress,
59
+ success: success_block_for_http_method(:post, inner_callback),
60
+ failure: failure_block(inner_callback))
95
61
  end
96
- if upload_callback
97
- if operation_or_task.is_a?(AFURLConnectionOperation)
98
- operation_or_task.setUploadProgressBlock(upload_callback)
99
- else
100
- # using NSURLSession - messy, probably leaks
101
- @observer = SessionObserver.new(operation_or_task, upload_callback)
102
- end
62
+ task
63
+ end
64
+
65
+ def create_task(http_method, path, options = {}, &callback)
66
+ parameters = options.fetch(:params, {})
67
+ headers = options.fetch(:headers, {})
68
+ progress = options[:progress_block]
69
+
70
+ method_signature = "#{http_method.to_s.upcase}:parameters:headers:progress:success:failure"
71
+ success = success_block_for_http_method(http_method, callback)
72
+ failure = failure_block(callback)
73
+ method_and_args = [method_signature, path, parameters, headers, progress, success, failure]
74
+
75
+ # HEAD doesn't take a progress arg
76
+ if http_method.to_s.upcase == "HEAD"
77
+ method_signature.gsub!("progress:", "")
78
+ method_and_args.delete_at(4)
103
79
  end
104
- operation_or_task
80
+
81
+ self.public_send(*method_and_args)
105
82
  end
106
83
 
107
- def create_operation(http_method, path, parameters = {}, &callback)
108
- method_signature = "#{http_method.upcase}:parameters:success:failure:"
109
- method = self.method(method_signature)
110
- success_block = AFMotion::Operation.success_block_for_http_method(http_method, callback)
111
- failure_block = AFMotion::Operation.failure_block(callback)
112
- operation = method.call(path, parameters, success_block, failure_block)
113
- if parameters && parameters[:progress_block] && operation.respond_to?(:setDownloadProgressBlock)
114
- operation.setDownloadProgressBlock(parameters.delete(:progress_block))
84
+ def success_block_for_http_method(http_method, callback)
85
+ if http_method.downcase.to_sym == :head
86
+ return ->(task) {
87
+ result = AFMotion::HTTPResult.new(task, nil, nil)
88
+ callback.call(result)
89
+ }
115
90
  end
116
- operation
91
+
92
+ ->(task, responseObject) {
93
+ result = AFMotion::HTTPResult.new(task, responseObject, nil)
94
+ callback.call(result)
95
+ }
117
96
  end
118
97
 
119
- alias_method :create_task, :create_operation
98
+ def failure_block(callback)
99
+ ->(task, error) {
100
+ result = AFMotion::HTTPResult.new(task, nil, error)
101
+ callback.call(result)
102
+ }
103
+ end
120
104
 
121
105
  private
122
106
  # To force RubyMotion pre-compilation of these methods
123
107
  def dummy
124
- self.GET("", parameters: nil, success: nil, failure: nil)
125
- self.HEAD("", parameters: nil, success: nil, failure: nil)
126
- self.POST("", parameters: nil, success: nil, failure: nil)
127
- self.POST("", parameters: nil, constructingBodyWithBlock: nil, success: nil, failure: nil)
128
- self.PUT("", parameters: nil, success: nil, failure: nil)
129
- self.DELETE("", parameters: nil, success: nil, failure: nil)
130
- self.PATCH("", parameters: nil, success: nil, failure: nil)
108
+ self.GET("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil)
109
+ self.HEAD("", parameters: nil, headers: nil, success: nil, failure: nil)
110
+ self.POST("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil)
111
+ self.POST("", parameters: nil, headers: nil, constructingBodyWithBlock: nil, progress: nil, success: nil, failure: nil)
112
+ self.PUT("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil)
113
+ self.DELETE("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil)
114
+ self.PATCH("", parameters: nil, headers: nil, progress: nil, success: nil, failure: nil)
131
115
  end
132
116
  end
133
117
  end
@@ -0,0 +1,73 @@
1
+ motion_require '../client_shared'
2
+
3
+ module AFMotion
4
+ module Serialization
5
+ def with_request_serializer(serializer_klass)
6
+ self.requestSerializer = serializer_klass.serializer
7
+ self
8
+ end
9
+
10
+ def with_response_serializer(serializer_klass)
11
+ self.responseSerializer = serializer_klass.serializer
12
+ self
13
+ end
14
+
15
+ def http!
16
+ with_request_serializer(AFHTTPRequestSerializer).
17
+ with_response_serializer(AFHTTPResponseSerializer)
18
+ end
19
+
20
+ def json!
21
+ with_request_serializer(AFJSONRequestSerializer).
22
+ with_response_serializer(AFJSONResponseSerializer)
23
+ end
24
+
25
+ def xml!
26
+ with_response_serializer(AFXMLParserResponseSerializer)
27
+ end
28
+
29
+ def plist!
30
+ with_request_serializer(AFPropertyListRequestSerializer).
31
+ with_response_serializer(AFPropertyListResponseSerializer)
32
+ end
33
+
34
+ def image!
35
+ with_response_serializer(AFImageResponseSerializer)
36
+ end
37
+ end
38
+ end
39
+
40
+ class AFHTTPSessionManager
41
+ include AFMotion::Serialization
42
+ include AFMotion::ClientShared
43
+
44
+ AFMotion::HTTP_METHODS.each do |method|
45
+ # EX client.get('my/resource.json')
46
+ define_method "#{method}", -> (path, options = {}, &callback) do
47
+ create_task(method, path, options, &callback)
48
+ end
49
+ end
50
+
51
+ # options = {parameters: , constructingBodyWithBlock: , success:, failure:}
52
+ def PUT(url_string, options = {})
53
+ parameters = options[:parameters]
54
+ block = options[:constructingBodyWithBlock]
55
+ progress = options[:progress_block]
56
+ success = options[:success]
57
+ failure = options[:failure]
58
+
59
+ request = self.requestSerializer.multipartFormRequestWithMethod("PUT", URLString: NSURL.URLWithString(url_string, relativeToURL: self.baseURL).absoluteString, parameters:parameters, constructingBodyWithBlock:block, error:nil)
60
+
61
+ task = self.dataTaskWithRequest(request, uploadProgress: progress, downloadProgress: nil, completionHandler: ->(response, responseObject, error) {
62
+ if error && failure
63
+ failure.call(task, error)
64
+ elsif success
65
+ success.call(task, responseObject)
66
+ end
67
+ })
68
+
69
+ task.resume
70
+
71
+ task
72
+ end
73
+ end